mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-20 22:17:26 +00:00
Compare commits
27 Commits
v0.24.0-be
...
doc/0.24.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
453b000711 | ||
![]() |
e88406e837 | ||
![]() |
e4a3dcc3b8 | ||
![]() |
caad5c613d | ||
![]() |
38aef77e54 | ||
![]() |
1ab7b315a2 | ||
![]() |
610597bfb7 | ||
![]() |
ede4f97a16 | ||
![]() |
fa641e38b8 | ||
![]() |
41bad2b9fd | ||
![]() |
f9bbfa5eab | ||
![]() |
b81420bef1 | ||
![]() |
9313e5b058 | ||
![]() |
770f3dcb93 | ||
![]() |
af4508b9dc | ||
![]() |
bbc93a90a2 | ||
![]() |
0acb2b5647 | ||
![]() |
3269cfdca0 | ||
![]() |
319ce67c87 | ||
![]() |
47b405d6c6 | ||
![]() |
65304a0ce7 | ||
![]() |
e270169c13 | ||
![]() |
7d937c6bd0 | ||
![]() |
ccc895b4c6 | ||
![]() |
5345f19693 | ||
![]() |
ec8729b772 | ||
![]() |
e00b9d9a91 |
1
.github/workflows/test-integration.yaml
vendored
1
.github/workflows/test-integration.yaml
vendored
@@ -25,6 +25,7 @@ jobs:
|
||||
- TestOIDCAuthenticationPingAll
|
||||
- TestOIDCExpireNodesBasedOnTokenExpiry
|
||||
- TestOIDC024UserCreation
|
||||
- TestOIDCAuthenticationWithPKCE
|
||||
- TestAuthWebFlowAuthenticationPingAll
|
||||
- TestAuthWebFlowLogoutAndRelogin
|
||||
- TestUserCommand
|
||||
|
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -34,4 +34,10 @@ jobs:
|
||||
|
||||
- name: Run tests
|
||||
if: steps.changed-files.outputs.files == 'true'
|
||||
env:
|
||||
# As of 2025-01-06, these env vars was not automatically
|
||||
# set anymore which breaks the initdb for postgres on
|
||||
# some of the database migration tests.
|
||||
LC_ALL: "en_US.UTF-8"
|
||||
LC_CTYPE: "en_US.UTF-8"
|
||||
run: nix develop --command -- gotestsum
|
||||
|
21
CHANGELOG.md
21
CHANGELOG.md
@@ -2,7 +2,7 @@
|
||||
|
||||
## Next
|
||||
|
||||
## 0.24.0 (2024-xx-xx)
|
||||
## 0.24.0 (2024-01-17)
|
||||
|
||||
### Security fix: OIDC changes in Headscale 0.24.0
|
||||
|
||||
@@ -153,7 +153,7 @@ This will also affect the way you
|
||||
|
||||
### Changes
|
||||
|
||||
- Improved compatibilty of built-in DERP server with clients connecting over
|
||||
- Improved compatibility of built-in DERP server with clients connecting over
|
||||
WebSocket [#2132](https://github.com/juanfont/headscale/pull/2132)
|
||||
- Allow nodes to use SSH agent forwarding
|
||||
[#2145](https://github.com/juanfont/headscale/pull/2145)
|
||||
@@ -164,13 +164,15 @@ This will also affect the way you
|
||||
- Fixed updating of hostname and givenName when it is updated in HostInfo
|
||||
[#2199](https://github.com/juanfont/headscale/pull/2199)
|
||||
- Fixed missing `stable-debug` container tag
|
||||
[#2232](https://github.com/juanfont/headscale/pr/2232)
|
||||
[#2232](https://github.com/juanfont/headscale/pull/2232)
|
||||
- Loosened up `server_url` and `base_domain` check. It was overly strict in some
|
||||
cases. [#2248](https://github.com/juanfont/headscale/pull/2248)
|
||||
- CLI for managing users now accepts `--identifier` in addition to `--name`,
|
||||
usage of `--identifier` is recommended
|
||||
[#2261](https://github.com/juanfont/headscale/pull/2261)
|
||||
- Add `dns.extra_records_path` configuration option [#2262](https://github.com/juanfont/headscale/issues/2262)
|
||||
- Support client verify for DERP [#2046](https://github.com/juanfont/headscale/pull/2046)
|
||||
- Add PKCE Verifier for OIDC [#2314](https://github.com/juanfont/headscale/pull/2314)
|
||||
|
||||
## 0.23.0 (2024-09-18)
|
||||
|
||||
@@ -253,14 +255,14 @@ part of adopting [#1460](https://github.com/juanfont/headscale/pull/1460).
|
||||
- Entrypoint of container image has changed from shell to headscale, require
|
||||
change from `headscale serve` to `serve`
|
||||
- `/var/lib/headscale` and `/var/run/headscale` is no longer created
|
||||
automatically, see [container docs](./docs/running-headscale-container.md)
|
||||
automatically, see [container docs](./docs/setup/install/container.md)
|
||||
- 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
|
||||
- This is in preparation 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
|
||||
@@ -643,7 +645,7 @@ part of adopting [#1460](https://github.com/juanfont/headscale/pull/1460).
|
||||
|
||||
- Boundaries between Namespaces has been removed and all nodes can communicate
|
||||
by default [#357](https://github.com/juanfont/headscale/pull/357)
|
||||
- To limit access between nodes, use [ACLs](./docs/acls.md).
|
||||
- To limit access between nodes, use [ACLs](./docs/ref/acls.md).
|
||||
- `/metrics` is now a configurable host:port endpoint:
|
||||
[#344](https://github.com/juanfont/headscale/pull/344). You must update your
|
||||
`config.yaml` file to include:
|
||||
@@ -701,13 +703,12 @@ behaviour.
|
||||
- All machines can communicate with all machines by default
|
||||
- Tags should now work correctly and adding a host to Headscale should now
|
||||
reload the rules.
|
||||
- The documentation have a [fictional example](docs/acls.md) that should cover
|
||||
- The documentation have a [fictional example](./docs/ref/acls.md) that should cover
|
||||
some use cases of the ACLs features
|
||||
|
||||
### Features
|
||||
|
||||
- Add support for configurable mTLS
|
||||
[docs](docs/tls.md#configuring-mutual-tls-authentication-mtls)
|
||||
- Add support for configurable mTLS [docs](./docs/ref/tls.md)
|
||||
[#297](https://github.com/juanfont/headscale/pull/297)
|
||||
|
||||
### Changes
|
||||
@@ -721,7 +722,7 @@ behaviour.
|
||||
|
||||
- Add IPv6 support to the prefix assigned to namespaces
|
||||
- Add API Key support
|
||||
- Enable remote control of `headscale` via CLI [docs](docs/remote-cli.md)
|
||||
- Enable remote control of `headscale` via CLI [docs](./docs/ref/remote-cli.md)
|
||||
- Enable HTTP API (beta, subject to change)
|
||||
- OpenID Connect users will be mapped per namespaces
|
||||
- Each user will get its own namespace, created if it does not exist
|
||||
|
4
Makefile
4
Makefile
@@ -22,7 +22,7 @@ build:
|
||||
dev: lint test build
|
||||
|
||||
test:
|
||||
gotestsum -- -short -coverprofile=coverage.out ./...
|
||||
gotestsum -- -short -race -coverprofile=coverage.out ./...
|
||||
|
||||
test_integration:
|
||||
docker run \
|
||||
@@ -33,7 +33,7 @@ test_integration:
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v $$PWD/control_logs:/tmp/control \
|
||||
golang:1 \
|
||||
go run gotest.tools/gotestsum@latest -- -failfast ./... -timeout 120m -parallel 8
|
||||
go run gotest.tools/gotestsum@latest -- -race -failfast ./... -timeout 120m -parallel 8
|
||||
|
||||
lint:
|
||||
golangci-lint run --fix --timeout 10m
|
||||
|
@@ -39,33 +39,33 @@ func init() {
|
||||
|
||||
err := registerNodeCmd.MarkFlagRequired("user")
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
registerNodeCmd.Flags().StringP("key", "k", "", "Key")
|
||||
err = registerNodeCmd.MarkFlagRequired("key")
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
nodeCmd.AddCommand(registerNodeCmd)
|
||||
|
||||
expireNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
|
||||
err = expireNodeCmd.MarkFlagRequired("identifier")
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
nodeCmd.AddCommand(expireNodeCmd)
|
||||
|
||||
renameNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
|
||||
err = renameNodeCmd.MarkFlagRequired("identifier")
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
nodeCmd.AddCommand(renameNodeCmd)
|
||||
|
||||
deleteNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
|
||||
err = deleteNodeCmd.MarkFlagRequired("identifier")
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
nodeCmd.AddCommand(deleteNodeCmd)
|
||||
|
||||
@@ -73,7 +73,7 @@ func init() {
|
||||
|
||||
err = moveNodeCmd.MarkFlagRequired("identifier")
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
moveNodeCmd.Flags().StringP("user", "u", "", "New user")
|
||||
@@ -85,7 +85,7 @@ func init() {
|
||||
|
||||
err = moveNodeCmd.MarkFlagRequired("user")
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
nodeCmd.AddCommand(moveNodeCmd)
|
||||
|
||||
@@ -93,7 +93,7 @@ func init() {
|
||||
|
||||
err = tagCmd.MarkFlagRequired("identifier")
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
tagCmd.Flags().
|
||||
StringSliceP("tags", "t", []string{}, "List of tags to add to the node")
|
||||
|
@@ -25,21 +25,21 @@ func init() {
|
||||
enableRouteCmd.Flags().Uint64P("route", "r", 0, "Route identifier (ID)")
|
||||
err := enableRouteCmd.MarkFlagRequired("route")
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
routesCmd.AddCommand(enableRouteCmd)
|
||||
|
||||
disableRouteCmd.Flags().Uint64P("route", "r", 0, "Route identifier (ID)")
|
||||
err = disableRouteCmd.MarkFlagRequired("route")
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
routesCmd.AddCommand(disableRouteCmd)
|
||||
|
||||
deleteRouteCmd.Flags().Uint64P("route", "r", 0, "Route identifier (ID)")
|
||||
err = deleteRouteCmd.MarkFlagRequired("route")
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
routesCmd.AddCommand(deleteRouteCmd)
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package cli
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
survey "github.com/AlecAivazis/survey/v2"
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
@@ -40,6 +41,9 @@ func usernameAndIDFromFlag(cmd *cobra.Command) (uint64, string) {
|
||||
func init() {
|
||||
rootCmd.AddCommand(userCmd)
|
||||
userCmd.AddCommand(createUserCmd)
|
||||
createUserCmd.Flags().StringP("display-name", "d", "", "Display name")
|
||||
createUserCmd.Flags().StringP("email", "e", "", "Email")
|
||||
createUserCmd.Flags().StringP("picture-url", "p", "", "Profile picture URL")
|
||||
userCmd.AddCommand(listUsersCmd)
|
||||
usernameAndIDFlag(listUsersCmd)
|
||||
listUsersCmd.Flags().StringP("email", "e", "", "Email")
|
||||
@@ -83,6 +87,28 @@ var createUserCmd = &cobra.Command{
|
||||
|
||||
request := &v1.CreateUserRequest{Name: userName}
|
||||
|
||||
if displayName, _ := cmd.Flags().GetString("display-name"); displayName != "" {
|
||||
request.DisplayName = displayName
|
||||
}
|
||||
|
||||
if email, _ := cmd.Flags().GetString("email"); email != "" {
|
||||
request.Email = email
|
||||
}
|
||||
|
||||
if pictureURL, _ := cmd.Flags().GetString("picture-url"); pictureURL != "" {
|
||||
if _, err := url.Parse(pictureURL); err != nil {
|
||||
ErrorOutput(
|
||||
err,
|
||||
fmt.Sprintf(
|
||||
"Invalid Picture URL: %s",
|
||||
err,
|
||||
),
|
||||
output,
|
||||
)
|
||||
}
|
||||
request.PictureUrl = pictureURL
|
||||
}
|
||||
|
||||
log.Trace().Interface("request", request).Msg("Sending CreateUser request")
|
||||
response, err := client.CreateUser(ctx, request)
|
||||
if err != nil {
|
||||
|
@@ -301,7 +301,7 @@ dns:
|
||||
search_domains: []
|
||||
|
||||
# Extra DNS records
|
||||
# so far only A-records are supported (on the tailscale side)
|
||||
# so far only A and AAAA records are supported (on the tailscale side)
|
||||
# See: docs/ref/dns.md
|
||||
extra_records: []
|
||||
# - name: "grafana.myvpn.example.com"
|
||||
@@ -310,6 +310,10 @@ dns:
|
||||
#
|
||||
# # you can also put it in one line
|
||||
# - { name: "prometheus.myvpn.example.com", type: "A", value: "100.64.0.3" }
|
||||
#
|
||||
# Alternatively, extra DNS records can be loaded from a JSON file.
|
||||
# Headscale processes this file on each change.
|
||||
# extra_records_path: /var/lib/headscale/extra-records.json
|
||||
|
||||
# Unix socket used for the CLI to connect without authentication
|
||||
# Note: for production you will want to set this to something like:
|
||||
@@ -360,6 +364,18 @@ unix_socket_permission: "0770"
|
||||
# allowed_users:
|
||||
# - alice@example.com
|
||||
#
|
||||
# # Optional: PKCE (Proof Key for Code Exchange) configuration
|
||||
# # PKCE adds an additional layer of security to the OAuth 2.0 authorization code flow
|
||||
# # by preventing authorization code interception attacks
|
||||
# # See https://datatracker.ietf.org/doc/html/rfc7636
|
||||
# pkce:
|
||||
# # Enable or disable PKCE support (default: false)
|
||||
# enabled: false
|
||||
# # PKCE method to use:
|
||||
# # - plain: Use plain code verifier
|
||||
# # - S256: Use SHA256 hashed code verifier (default, recommended)
|
||||
# method: S256
|
||||
#
|
||||
# # Map legacy users from pre-0.24.0 versions of headscale to the new OIDC users
|
||||
# # by taking the username from the legacy user and matching it with the username
|
||||
# # provided by the OIDC. This is useful when migrating from legacy users to OIDC
|
||||
|
@@ -12,7 +12,7 @@ provides on overview of headscale's feature and compatibility with the Tailscale
|
||||
- [x] [MagicDNS](https://tailscale.com/kb/1081/magicdns)
|
||||
- [x] [Global and restricted nameservers (split DNS)](https://tailscale.com/kb/1054/dns#nameservers)
|
||||
- [x] [search domains](https://tailscale.com/kb/1054/dns#search-domains)
|
||||
- [x] [Extra DNS records (headscale only)](../ref/dns.md#setting-custom-dns-records)
|
||||
- [x] [Extra DNS records (headscale only)](../ref/dns.md#setting-extra-dns-records)
|
||||
- [x] [Taildrop (File Sharing)](https://tailscale.com/kb/1106/taildrop)
|
||||
- [x] Routing advertising (including exit nodes)
|
||||
- [x] Dual stack (IPv4 and IPv6)
|
||||
@@ -25,6 +25,7 @@ provides on overview of headscale's feature and compatibility with the Tailscale
|
||||
- [ ] `autogroup:member`
|
||||
* [ ] Node registration using Single-Sign-On (OpenID Connect) ([GitHub label "OIDC"](https://github.com/juanfont/headscale/labels/OIDC))
|
||||
- [x] Basic registration
|
||||
- [ ] Update user profile from identity provider
|
||||
- [ ] Dynamic ACL support
|
||||
- [ ] OIDC groups cannot be used in ACLs
|
||||
- [ ] [Funnel](https://tailscale.com/kb/1223/funnel) ([#1040](https://github.com/juanfont/headscale/issues/1040))
|
||||
|
@@ -40,9 +40,6 @@ servers.
|
||||
|
||||
## ACL setup
|
||||
|
||||
Note: Users will be created automatically when users authenticate with the
|
||||
headscale server.
|
||||
|
||||
ACLs have to be written in [huJSON](https://github.com/tailscale/hujson).
|
||||
|
||||
When [registering the servers](../usage/getting-started.md#register-a-node) we
|
||||
@@ -52,11 +49,17 @@ tags to a server they can register, the check of the tags is done on headscale
|
||||
server and only valid tags are applied. A tag is valid if the user that is
|
||||
registering it is allowed to do it.
|
||||
|
||||
To use ACLs in headscale, you must edit your `config.yaml` file. In there you will find a `policy.path` parameter. This will need to point to your ACL file. More info on how these policies are written can be found [here](https://tailscale.com/kb/1018/acls/).
|
||||
To use ACLs in headscale, you must edit your `config.yaml` file. In there you will find a `policy.path` parameter. This
|
||||
will need to point to your ACL file. More info on how these policies are written can be found
|
||||
[here](https://tailscale.com/kb/1018/acls/).
|
||||
|
||||
Please reload or restart Headscale after updating the ACL file. Headscale may be reloaded either via its systemd service
|
||||
(`sudo systemctl reload headscale`) or by sending a SIGHUP signal (`sudo kill -HUP $(pidof headscale)`) to the main
|
||||
process. Headscale logs the result of ACL policy processing after each reload.
|
||||
|
||||
Here are the ACL's to implement the same permissions as above:
|
||||
|
||||
```json
|
||||
```json title="acl.json"
|
||||
{
|
||||
// groups are collections of users having a common scope. A user can be in multiple groups
|
||||
// groups cannot be composed of groups
|
||||
|
@@ -1,44 +1,76 @@
|
||||
# DNS
|
||||
|
||||
Headscale supports [most DNS features](../about/features.md) from Tailscale and DNS releated settings can be configured
|
||||
in the [configuration file](./configuration.md) within the `dns` section.
|
||||
Headscale supports [most DNS features](../about/features.md) from Tailscale. DNS related settings can be configured
|
||||
within `dns` section of the [configuration file](./configuration.md).
|
||||
|
||||
## Setting custom DNS records
|
||||
## Setting extra DNS records
|
||||
|
||||
!!! warning "Community documentation"
|
||||
Headscale allows to set extra DNS records which are made available via
|
||||
[MagicDNS](https://tailscale.com/kb/1081/magicdns). Extra DNS records can be configured either via static entries in the
|
||||
[configuration file](./configuration.md) or from a JSON file that Headscale continuously watches for changes:
|
||||
|
||||
This page is not actively maintained by the headscale authors and is
|
||||
written by community members. It is _not_ verified by headscale developers.
|
||||
* Use the `dns.extra_records` option in the [configuration file](./configuration.md) for entries that are static and
|
||||
don't change while Headscale is running. Those entries are processed when Headscale is starting up and changes to the
|
||||
configuration require a restart of Headscale.
|
||||
* For dynamic DNS records that may be added, updated or removed while Headscale is running or DNS records that are
|
||||
generated by scripts the option `dns.extra_records_path` in the [configuration file](./configuration.md) is useful.
|
||||
Set it to the absolute path of the JSON file containing DNS records and Headscale processes this file as it detects
|
||||
changes.
|
||||
|
||||
**It might be outdated and it might miss necessary steps**.
|
||||
|
||||
Headscale allows to set custom DNS records which are made available via
|
||||
[MagicDNS](https://tailscale.com/kb/1081/magicdns). An example use case is to serve multiple apps on the same host via a
|
||||
reverse proxy like NGINX, in this case a Prometheus monitoring stack. This allows to nicely access the service with
|
||||
"http://grafana.myvpn.example.com" instead of the hostname and port combination
|
||||
"http://hostname-in-magic-dns.myvpn.example.com:3000".
|
||||
An example use case is to serve multiple apps on the same host via a reverse proxy like NGINX, in this case a Prometheus
|
||||
monitoring stack. This allows to nicely access the service with "http://grafana.myvpn.example.com" instead of the
|
||||
hostname and port combination "http://hostname-in-magic-dns.myvpn.example.com:3000".
|
||||
|
||||
!!! warning "Limitations"
|
||||
|
||||
[Not all types of records are supported](https://github.com/tailscale/tailscale/blob/6edf357b96b28ee1be659a70232c0135b2ffedfd/ipn/ipnlocal/local.go#L2989-L3007), especially no CNAME records.
|
||||
Currently, [only A and AAAA records are processed by Tailscale](https://github.com/tailscale/tailscale/blob/v1.78.3/ipn/ipnlocal/local.go#L4461-L4479).
|
||||
|
||||
1. Update the [configuration file](./configuration.md) to contain the desired records like so:
|
||||
|
||||
```yaml
|
||||
1. Configure extra DNS records using one of the available configuration options:
|
||||
|
||||
=== "Static entries, via `dns.extra_records`"
|
||||
|
||||
```yaml title="config.yaml"
|
||||
dns:
|
||||
...
|
||||
extra_records:
|
||||
- name: "prometheus.myvpn.example.com"
|
||||
- name: "grafana.myvpn.example.com"
|
||||
type: "A"
|
||||
value: "100.64.0.3"
|
||||
|
||||
- name: "grafana.myvpn.example.com"
|
||||
- name: "prometheus.myvpn.example.com"
|
||||
type: "A"
|
||||
value: "100.64.0.3"
|
||||
...
|
||||
```
|
||||
|
||||
1. Restart your headscale instance.
|
||||
Restart your headscale instance.
|
||||
|
||||
=== "Dynamic entries, via `dns.extra_records_path`"
|
||||
|
||||
```json title="extra-records.json"
|
||||
[
|
||||
{
|
||||
"name": "grafana.myvpn.example.com",
|
||||
"type": "A",
|
||||
"value": "100.64.0.3"
|
||||
},
|
||||
{
|
||||
"name": "prometheus.myvpn.example.com",
|
||||
"type": "A",
|
||||
"value": "100.64.0.3"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Headscale picks up changes to the above JSON file automatically.
|
||||
|
||||
!!! tip "Good to know"
|
||||
|
||||
* The `dns.extra_records_path` option in the [configuration file](./configuration.md) needs to reference the
|
||||
JSON file containing extra DNS records.
|
||||
* Be sure to "sort keys" and produce a stable output in case you generate the JSON file with a script.
|
||||
Headscale uses a checksum to detect changes to the file and a stable output avoids unnecessary processing.
|
||||
|
||||
1. Verify that DNS records are properly set using the DNS querying tool of your choice:
|
||||
|
||||
@@ -61,7 +93,7 @@ reverse proxy like NGINX, in this case a Prometheus monitoring stack. This allow
|
||||
The motivating example here was to be able to access internal monitoring services on the same host without
|
||||
specifying a port, depicted as NGINX configuration snippet:
|
||||
|
||||
```
|
||||
```nginx title="nginx.conf"
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
@@ -23,7 +23,7 @@ Running headscale behind a cloudflare proxy or cloudflare tunnel is not supporte
|
||||
|
||||
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.
|
||||
|
||||
```yaml
|
||||
```yaml title="config.yaml"
|
||||
server_url: https://<YOUR_SERVER_NAME> # This should be the FQDN at which headscale will be served
|
||||
listen_addr: 0.0.0.0:8080
|
||||
metrics_listen_addr: 0.0.0.0:9090
|
||||
@@ -35,7 +35,7 @@ tls_key_path: ""
|
||||
|
||||
The following example configuration can be used in your nginx setup, substituting values as necessary. `<IP:PORT>` should be the IP address and port where headscale is running. In most cases, this will be `http://localhost:8080`.
|
||||
|
||||
```Nginx
|
||||
```nginx title="nginx.conf"
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
@@ -113,7 +113,7 @@ spec:
|
||||
|
||||
The following Caddyfile is all that is necessary to use Caddy as a reverse proxy for headscale, in combination with the `config.yaml` specifications above to disable headscale's built in TLS. Replace values as necessary - `<YOUR_SERVER_NAME>` should be the FQDN at which headscale will be served, and `<IP:PORT>` should be the IP address and port where headscale is running. In most cases, this will be `localhost:8080`.
|
||||
|
||||
```
|
||||
```none title="Caddyfile"
|
||||
<YOUR_SERVER_NAME> {
|
||||
reverse_proxy <IP:PORT>
|
||||
}
|
||||
@@ -127,7 +127,7 @@ For a slightly more complex configuration which utilizes Docker containers to ma
|
||||
|
||||
The following minimal Apache config will proxy traffic to the headscale instance on `<IP:PORT>`. Note that `upgrade=any` is required as a parameter for `ProxyPass` so that WebSockets traffic whose `Upgrade` header value is not equal to `WebSocket` (i. e. Tailscale Control Protocol) is forwarded correctly. See the [Apache docs](https://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html) for more information on this.
|
||||
|
||||
```
|
||||
```apache title="apache.conf"
|
||||
<VirtualHost *:443>
|
||||
ServerName <YOUR_SERVER_NAME>
|
||||
|
||||
|
@@ -11,7 +11,7 @@ Headscale doesn't provide a built-in web interface but users may pick one from t
|
||||
| --------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
||||
| headscale-webui | [Github](https://github.com/ifargle/headscale-webui) | A simple headscale web UI for small-scale deployments. |
|
||||
| headscale-ui | [Github](https://github.com/gurucomputing/headscale-ui) | A web frontend for the headscale Tailscale-compatible coordination server |
|
||||
| HeadscaleUi | [GitHub](https://github.com/simcu/headscale-ui) | A static headscale admin ui, no backend enviroment required |
|
||||
| HeadscaleUi | [GitHub](https://github.com/simcu/headscale-ui) | A static headscale admin ui, no backend environment required |
|
||||
| Headplane | [GitHub](https://github.com/tale/headplane) | An advanced Tailscale inspired frontend for headscale |
|
||||
| headscale-admin | [Github](https://github.com/GoodiesHQ/headscale-admin) | Headscale-Admin is meant to be a simple, modern web interface for headscale |
|
||||
| ouroboros | [Github](https://github.com/yellowsink/ouroboros) | Ouroboros is designed for users to manage their own devices, rather than for admins |
|
||||
|
@@ -11,7 +11,7 @@ Known limitations:
|
||||
|
||||
In your `config.yaml`, customize this to your liking:
|
||||
|
||||
```yaml
|
||||
```yaml title="config.yaml"
|
||||
oidc:
|
||||
# Block further startup until the OIDC provider is healthy and available
|
||||
only_start_if_oidc_is_available: true
|
||||
@@ -45,6 +45,18 @@ oidc:
|
||||
allowed_users:
|
||||
- alice@example.com
|
||||
|
||||
# Optional: PKCE (Proof Key for Code Exchange) configuration
|
||||
# PKCE adds an additional layer of security to the OAuth 2.0 authorization code flow
|
||||
# by preventing authorization code interception attacks
|
||||
# See https://datatracker.ietf.org/doc/html/rfc7636
|
||||
pkce:
|
||||
# Enable or disable PKCE support (default: false)
|
||||
enabled: false
|
||||
# PKCE method to use:
|
||||
# - plain: Use plain code verifier
|
||||
# - S256: Use SHA256 hashed code verifier (default, recommended)
|
||||
method: S256
|
||||
|
||||
# If `strip_email_domain` is set to `true`, the domain part of the username email address will be removed.
|
||||
# This will transform `first-name.last-name@example.com` to the user `first-name.last-name`
|
||||
# If `strip_email_domain` is set to `false` the domain part will NOT be removed resulting to the following
|
||||
@@ -56,7 +68,7 @@ oidc:
|
||||
|
||||
In order to integrate headscale with Azure Active Directory, we'll need to provision an App Registration with the correct scopes and redirect URI. Here with Terraform:
|
||||
|
||||
```hcl
|
||||
```hcl title="terraform.hcl"
|
||||
resource "azuread_application" "headscale" {
|
||||
display_name = "Headscale"
|
||||
|
||||
@@ -127,7 +139,7 @@ output "headscale_client_secret" {
|
||||
|
||||
And in your headscale `config.yaml`:
|
||||
|
||||
```yaml
|
||||
```yaml title="config.yaml"
|
||||
oidc:
|
||||
issuer: "https://login.microsoftonline.com/<tenant-UUID>/v2.0"
|
||||
client_id: "<client-id-from-terraform>"
|
||||
@@ -162,7 +174,7 @@ However if you don't have a domain, or need to add users outside of your domain,
|
||||
8. Click `Save` at the bottom of the form
|
||||
9. Take note of the `Client ID` and `Client secret`, you can also download it for reference if you need it.
|
||||
10. Edit your headscale config, under `oidc`, filling in your `client_id` and `client_secret`:
|
||||
```yaml
|
||||
```yaml title="config.yaml"
|
||||
oidc:
|
||||
issuer: "https://accounts.google.com"
|
||||
client_id: ""
|
||||
|
@@ -54,7 +54,7 @@ headscale apikeys expire --prefix "<PREFIX>"
|
||||
|
||||
=== "Minimal YAML configuration file"
|
||||
|
||||
```yaml
|
||||
```yaml title="config.yaml"
|
||||
cli:
|
||||
address: <HEADSCALE_ADDRESS>:<PORT>
|
||||
api_key: <API_KEY_FROM_PREVIOUS_STEP>
|
||||
@@ -69,7 +69,7 @@ headscale apikeys expire --prefix "<PREFIX>"
|
||||
|
||||
!!! bug
|
||||
|
||||
Headscale 0.23.0 requires at least an empty configuration file when environment variables are used to
|
||||
Headscale currently requires at least an empty configuration file when environment variables are used to
|
||||
specify connection details. See [issue 2193](https://github.com/juanfont/headscale/issues/2193) for more
|
||||
information.
|
||||
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
Headscale can be configured to expose its web service via TLS. To configure the certificate and key file manually, set the `tls_cert_path` and `tls_cert_path` configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.
|
||||
|
||||
```yaml
|
||||
```yaml title="config.yaml"
|
||||
tls_cert_path: ""
|
||||
tls_key_path: ""
|
||||
```
|
||||
@@ -15,7 +15,7 @@ The certificate should contain the full chain, else some clients, like the Tails
|
||||
|
||||
To get a certificate automatically via [Let's Encrypt](https://letsencrypt.org/), set `tls_letsencrypt_hostname` to the desired certificate hostname. This name must resolve to the IP address(es) headscale is reachable on (i.e., it must correspond to the `server_url` configuration parameter). The certificate and Let's Encrypt account credentials will be stored in the directory configured in `tls_letsencrypt_cache_dir`. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.
|
||||
|
||||
```yaml
|
||||
```yaml title="config.yaml"
|
||||
tls_letsencrypt_hostname: ""
|
||||
tls_letsencrypt_listen: ":http"
|
||||
tls_letsencrypt_cache_dir: ".cache"
|
||||
|
@@ -1,25 +0,0 @@
|
||||
# Running headscale in a cloud
|
||||
|
||||
!!! warning "Community documentation"
|
||||
|
||||
This page is not actively maintained by the headscale authors and is
|
||||
written by community members. It is _not_ verified by headscale developers.
|
||||
|
||||
**It might be outdated and it might miss necessary steps**.
|
||||
|
||||
## Sealos
|
||||
|
||||
[Deploy headscale as service on Sealos.](https://icloudnative.io/en/posts/how-to-set-up-or-migrate-headscale/)
|
||||
|
||||
1. Click the following prebuilt template:
|
||||
|
||||
[](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dheadscale)
|
||||
|
||||
2. Click "Deploy Application" on the template page to start deployment. Upon completion, two applications appear: headscale, and one of its [web interfaces](../../ref/integration/web-ui.md).
|
||||
3. Once deployment concludes, click 'Details' on the headscale application page to navigate to the application's details.
|
||||
4. Wait for the application's status to switch to running. For accessing the headscale server, the Public Address associated with port 8080 is the address of the headscale server. To access the headscale console, simply append `/admin/` to the headscale public URL.
|
||||
|
||||
!!! tip "Remote CLI"
|
||||
|
||||
Headscale can be managed remotely via its remote CLI support. See our [Controlling headscale with remote
|
||||
CLI](../../ref/remote-cli.md) documentation for details.
|
@@ -94,7 +94,7 @@ systemd.
|
||||
1. In `/etc/headscale/config.yaml`, override the default `headscale` unix socket with a path that is writable by the
|
||||
`headscale` user or group:
|
||||
|
||||
```yaml
|
||||
```yaml title="config.yaml"
|
||||
unix_socket: /var/run/headscale/headscale.sock
|
||||
```
|
||||
|
||||
|
@@ -16,7 +16,7 @@ README](https://github.com/juanfont/headscale#contributing) for more information
|
||||
### Install from source
|
||||
|
||||
```shell
|
||||
# Install prerequistes
|
||||
# Install prerequisites
|
||||
pkg_add go
|
||||
|
||||
git clone https://github.com/juanfont/headscale.git
|
||||
@@ -42,7 +42,7 @@ cp headscale /usr/local/sbin
|
||||
### Install from source via cross compile
|
||||
|
||||
```shell
|
||||
# Install prerequistes
|
||||
# Install prerequisites
|
||||
# 1. go v1.20+: headscale newer than 0.21 needs go 1.20+ to compile
|
||||
# 2. gmake: Makefile in the headscale repo is written in GNU make syntax
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Upgrade an existing installation
|
||||
|
||||
An existing headscale installation can be updated to a new version:
|
||||
Update an existing headscale installation to a new version:
|
||||
|
||||
- Read the announcement on the [GitHub releases](https://github.com/juanfont/headscale/releases) page for the new
|
||||
version. It lists the changes of the release along with possible breaking changes.
|
||||
|
@@ -15,14 +15,10 @@ Install the official Tailscale iOS client from the [App Store](https://apps.appl
|
||||
|
||||
### Configuring the headscale URL
|
||||
|
||||
- Open Tailscale and make sure you are _not_ logged in to any account
|
||||
- Open Settings on the iOS device
|
||||
- Scroll down to the `third party apps` section, under `Game Center` or `TV Provider`
|
||||
- Find Tailscale and select it
|
||||
- If the iOS device was previously logged into Tailscale, switch the `Reset Keychain` toggle to `on`
|
||||
- Enter the URL of your headscale instance (e.g `https://headscale.example.com`) under `Alternate Coordination Server URL`
|
||||
- Restart the app by closing it from the iOS app switcher, open the app and select the regular sign in option
|
||||
_(non-SSO)_. It should open up to the headscale authentication page.
|
||||
- Open the Tailscale app
|
||||
- Click the account icon in the top-right corner and select `Log in…`.
|
||||
- Tap the top-right options menu button and select `Use custom coordination server`.
|
||||
- Enter your instance url (e.g `https://headscale.example.com`)
|
||||
- Enter your credentials and log in. Headscale should now be working on your iOS device.
|
||||
|
||||
## macOS
|
||||
@@ -43,7 +39,7 @@ tailscale login --login-server <YOUR_HEADSCALE_URL>
|
||||
|
||||
#### GUI
|
||||
|
||||
- ALT + Click the Tailscale icon in the menu and hover over the Debug menu
|
||||
- Option + Click the Tailscale icon in the menu and hover over the Debug menu
|
||||
- Under `Custom Login Server`, select `Add Account...`
|
||||
- Enter the URL of your headscale instance (e.g `https://headscale.example.com`) and press `Add Account`
|
||||
- Follow the login procedure in the browser
|
||||
|
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1733376361,
|
||||
"narHash": "sha256-aLJxoTDDSqB+/3orsulE6/qdlX6MzDLIITLZqdgMpqo=",
|
||||
"lastModified": 1736420959,
|
||||
"narHash": "sha256-dMGNa5UwdtowEqQac+Dr0d2tFO/60ckVgdhZU9q2E2o=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "929116e316068c7318c54eb4d827f7d9756d5e9c",
|
||||
"rev": "32af3611f6f05655ca166a0b1f47b57c762b5192",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@@ -31,8 +31,8 @@
|
||||
checkFlags = ["-short"];
|
||||
|
||||
# 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-NyXMSIVcmPlUhE3LmEsYZQxJdz+e435r+GZC8umQKqQ=";
|
||||
# update this if you have a mismatch after doing a change to those files.
|
||||
vendorHash = "sha256-SBfeixT8DQOrK2SWmHHSOBtzRdSZs+pwomHpw6Jd+qc=";
|
||||
|
||||
subPackages = ["cmd/headscale"];
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc (unknown)
|
||||
// source: headscale/v1/apikey.proto
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc (unknown)
|
||||
// source: headscale/v1/device.proto
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc (unknown)
|
||||
// source: headscale/v1/headscale.proto
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc (unknown)
|
||||
// source: headscale/v1/node.proto
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc (unknown)
|
||||
// source: headscale/v1/policy.proto
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc (unknown)
|
||||
// source: headscale/v1/preauthkey.proto
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc (unknown)
|
||||
// source: headscale/v1/routes.proto
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc-gen-go v1.35.2
|
||||
// protoc (unknown)
|
||||
// source: headscale/v1/user.proto
|
||||
|
||||
@@ -128,6 +128,9 @@ type CreateUserRequest struct {
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"`
|
||||
Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
|
||||
PictureUrl string `protobuf:"bytes,4,opt,name=picture_url,json=pictureUrl,proto3" json:"picture_url,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CreateUserRequest) Reset() {
|
||||
@@ -167,6 +170,27 @@ func (x *CreateUserRequest) GetName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CreateUserRequest) GetDisplayName() string {
|
||||
if x != nil {
|
||||
return x.DisplayName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CreateUserRequest) GetEmail() string {
|
||||
if x != nil {
|
||||
return x.Email
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CreateUserRequest) GetPictureUrl() string {
|
||||
if x != nil {
|
||||
return x.PictureUrl
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type CreateUserResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -520,38 +544,43 @@ var file_headscale_v1_user_proto_rawDesc = []byte{
|
||||
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72,
|
||||
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c,
|
||||
0x65, 0x5f, 0x70, 0x69, 0x63, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x69, 0x63, 0x55, 0x72, 0x6c, 0x22, 0x27,
|
||||
0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3c, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74,
|
||||
0x0d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x69, 0x63, 0x55, 0x72, 0x6c, 0x22, 0x81,
|
||||
0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70,
|
||||
0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
|
||||
0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65,
|
||||
0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69,
|
||||
0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x75, 0x72, 0x6c,
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x55,
|
||||
0x72, 0x6c, 0x22, 0x3c, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
|
||||
0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72,
|
||||
0x22, 0x45, 0x0a, 0x11, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6f, 0x6c, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08,
|
||||
0x6e, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
||||
0x6e, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3c, 0x0a, 0x12, 0x52, 0x65, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a,
|
||||
0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x65,
|
||||
0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52,
|
||||
0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x45, 0x0a, 0x11, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x55,
|
||||
0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x6c,
|
||||
0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6f, 0x6c, 0x64, 0x49,
|
||||
0x64, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3c, 0x0a, 0x12,
|
||||
0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x12, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
|
||||
0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x23, 0x0a, 0x11, 0x44, 0x65,
|
||||
0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x22,
|
||||
0x14, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4c, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65,
|
||||
0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d,
|
||||
0x61, 0x69, 0x6c, 0x22, 0x3d, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72,
|
||||
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
|
||||
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65,
|
||||
0x72, 0x73, 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,
|
||||
0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x23, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55,
|
||||
0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x22, 0x14, 0x0a, 0x12, 0x44, 0x65,
|
||||
0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x4c, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
|
||||
0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69,
|
||||
0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x3d,
|
||||
0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03,
|
||||
0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 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 (
|
||||
|
@@ -1039,6 +1039,15 @@
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"pictureUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
106
go.mod
106
go.mod
@@ -4,50 +4,52 @@ go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/chasefleming/elem-go v0.29.0
|
||||
github.com/cenkalti/backoff/v4 v4.3.0
|
||||
github.com/chasefleming/elem-go v0.30.0
|
||||
github.com/coder/websocket v1.8.12
|
||||
github.com/coreos/go-oidc/v3 v3.11.0
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||
github.com/fsnotify/fsnotify v1.8.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.2
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3
|
||||
github.com/gofrs/uuid/v5 v5.3.0
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0
|
||||
github.com/jagottsicher/termcolor v1.0.2
|
||||
github.com/klauspost/compress v1.17.9
|
||||
github.com/klauspost/compress v1.17.11
|
||||
github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
|
||||
github.com/ory/dockertest/v3 v3.11.0
|
||||
github.com/philip-bui/grpc-zerolog v1.0.1
|
||||
github.com/pkg/profile v1.7.0
|
||||
github.com/prometheus/client_golang v1.20.2
|
||||
github.com/prometheus/common v0.58.0
|
||||
github.com/pterm/pterm v0.12.79
|
||||
github.com/prometheus/client_golang v1.20.5
|
||||
github.com/prometheus/common v0.61.0
|
||||
github.com/pterm/pterm v0.12.80
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/samber/lo v1.47.0
|
||||
github.com/sasha-s/go-deadlock v0.3.5
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.20.0-alpha.6
|
||||
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/stretchr/testify v1.10.0
|
||||
github.com/tailscale/hujson v0.0.0-20241010212012-29efb4a0184b
|
||||
github.com/tailscale/tailsql v0.0.0-20241211062219-bf96884c6a49
|
||||
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948
|
||||
golang.org/x/net v0.28.0
|
||||
golang.org/x/oauth2 v0.22.0
|
||||
golang.org/x/sync v0.8.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1
|
||||
google.golang.org/grpc v1.66.0
|
||||
google.golang.org/protobuf v1.35.1
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e
|
||||
golang.org/x/net v0.32.0
|
||||
golang.org/x/oauth2 v0.24.0
|
||||
golang.org/x/sync v0.10.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241216192217-9240e9c98484
|
||||
google.golang.org/grpc v1.69.0
|
||||
google.golang.org/protobuf v1.36.0
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/postgres v1.5.9
|
||||
gorm.io/gorm v1.25.11
|
||||
tailscale.com v1.75.0-pre.0.20240926101731-7d1160ddaab7
|
||||
gorm.io/driver/postgres v1.5.11
|
||||
gorm.io/gorm v1.25.12
|
||||
tailscale.com v1.79.0-pre
|
||||
zgo.at/zcache/v2 v2.1.0
|
||||
zombiezen.com/go/postgrestest v1.0.1
|
||||
)
|
||||
@@ -87,37 +89,35 @@ require (
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/akutz/memconn v0.1.0 // indirect
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.45.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
|
||||
github.com/aws/smithy-go v1.19.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 // indirect
|
||||
github.com/aws/smithy-go v1.20.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.13.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/containerd/console v1.0.4 // indirect
|
||||
github.com/containerd/continuity v0.4.5 // indirect
|
||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
||||
github.com/creachadair/mds v0.14.5 // indirect
|
||||
github.com/creachadair/mds v0.20.0 // indirect
|
||||
github.com/dblohm7/wingoes v0.0.0-20240123200102-b75a8a7d7eb0 // indirect
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
|
||||
github.com/docker/cli v27.3.1+incompatible // indirect
|
||||
github.com/docker/docker v27.3.1+incompatible // indirect
|
||||
github.com/docker/cli v27.4.0+incompatible // indirect
|
||||
github.com/docker/docker v27.4.0+incompatible // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/felixge/fgprof v0.9.5 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
|
||||
github.com/gaissmai/bart v0.11.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
@@ -135,7 +135,7 @@ require (
|
||||
github.com/google/go-github v17.0.0+incompatible // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
|
||||
github.com/google/pprof v0.0.0-20240829160300-da1f7e9f2b25 // indirect
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
@@ -148,8 +148,8 @@ require (
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240129002554-15c9b8791914 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.1 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
@@ -178,9 +178,9 @@ require (
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/opencontainers/runc v1.2.2 // indirect
|
||||
github.com/opencontainers/runc v1.2.3 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20241211131331-93ee7e083c43 // 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
|
||||
@@ -189,7 +189,7 @@ require (
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/safchain/ethtool v0.3.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.6.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
@@ -204,10 +204,10 @@ require (
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
|
||||
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect
|
||||
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 // indirect
|
||||
github.com/tailscale/setec v0.0.0-20240314234648-9da8e7407257 // indirect
|
||||
github.com/tailscale/squibble v0.0.0-20240418235321-9ee0eeb78185 // indirect
|
||||
github.com/tailscale/setec v0.0.0-20240930150730-e6eb93658ed3 // indirect
|
||||
github.com/tailscale/squibble v0.0.0-20240909231413-32a80b9743f7 // indirect
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect
|
||||
github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc // indirect
|
||||
github.com/tailscale/wireguard-go v0.0.0-20241113014420-4e883d38c8d3 // indirect
|
||||
github.com/tcnksm/go-httpstat v0.2.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
@@ -218,15 +218,15 @@ 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.20.0 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/term v0.27.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
golang.org/x/tools v0.28.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-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 // indirect
|
||||
)
|
||||
|
243
go.sum
243
go.sum
@@ -42,44 +42,44 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1L
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.6/go.mod h1:uKU6cnDmYCvJ+pxO9S4cWDb2yWWIH5hra+32hVh1MI4=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 h1:ugD6qzjYtB7zM5PN/ZIeaAIyefPaD82G8+SJopgvUpw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9/go.mod h1:YD0aYBWCrPENpHolhKw2XDlTIWae2GKXT1T4o6N6hiM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 h1:/90OR2XbSYfXucBMJ4U14wrjlfleq/0SB6dZDPncgmo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9/go.mod h1:dN/Of9/fNZet7UrQQ6kTDo/VSwKPIq94vjlU16bRARc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 h1:iEAeF6YC3l4FzlJPP9H3Ko1TXpdjdqWffxXjp8SY6uk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9/go.mod h1:kjsXoK23q9Z/tLBrckZLLyvjhZoS+AGrzqzUfEClvMM=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.7 h1:o0ASbVwUAIrfp/WcCac+6jioZt4Hd8k/1X8u7GJ/QeM=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.7/go.mod h1:vADO6Jn+Rq4nDtfwNjhgR84qkZwiC6FqCaXdw/kYwjA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.11 h1:f47rANd2LQEYHda2ddSCKYId18/8BhSRM4BULGmfgNA=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.11/go.mod h1:SMsV78RIOYdve1vf36z8LmnszlRWkwMQtomCAI0/mIE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHHvIq0l/pX3fwO+Tzs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5 h1:81KE7vaZzrl7yHBYHVEzYB8sypz11NMOZ40YlWvPxsU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5/go.mod h1:LIt2rg7Mcgn09Ygbdh/RdIm0rQ+3BNkbP1gyVMFtRK0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7 h1:ZMeFZ5yk+Ek+jNr1+uwCd2tG89t6oTS5yVWpa6yy2es=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7/go.mod h1:mxV05U+4JiHqIpGqqYXOHLPKUC6bDXC44bsUhNjOEwY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 h1:f9RyWNtS8oH7cZlbn+/JNPpjUk5+5fLd5lM9M0i49Ys=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5/go.mod h1:h5CoMZV2VF297/VLhRhO1WF+XYWOzXo+4HsObA4HjBQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1 h1:6cnno47Me9bRykw9AEv9zkXE+5or7jz8TsskTTccbgc=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1/go.mod h1:qmdkIIAC+GCLASF7R2whgNrJADz0QZPX+Seiw/i4S3o=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.45.0 h1:IOdss+igJDFdic9w3WKwxGCmHqUxydvIhJOm9LJ32Dk=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.45.0/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
|
||||
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
|
||||
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 h1:vN8hEbpRnL7+Hopy9dzmRle1xmDc7o8tmY0klsr175w=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.5/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 h1:Jux+gDDyi1Lruk+KHF91tK2KCuY61kzoCpvtvJJBtOE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 h1:cwIxeBttqPN3qkaAjcEcsh8NYr8n2HZPkcKgPAi1phU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
|
||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
@@ -90,8 +90,8 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chasefleming/elem-go v0.29.0 h1:WwrjQcVn6xldhexluvl2Z3sgKi9HTMuzWeEXO4PHsmg=
|
||||
github.com/chasefleming/elem-go v0.29.0/go.mod h1:hz73qILBIKnTgOujnSMtEj20/epI+f6vg71RUilJAA4=
|
||||
github.com/chasefleming/elem-go v0.30.0 h1:BlhV1ekv1RbFiM8XZUQeln1Ikb4D+bu2eDO4agREvok=
|
||||
github.com/chasefleming/elem-go v0.30.0/go.mod h1:hz73qILBIKnTgOujnSMtEj20/epI+f6vg71RUilJAA4=
|
||||
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
|
||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||
@@ -118,8 +118,8 @@ github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/
|
||||
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creachadair/mds v0.14.5 h1:2amuO4yCbQkaAyDoLO5iCbwbTRQZz4EpRhOejQbf4+8=
|
||||
github.com/creachadair/mds v0.14.5/go.mod h1:4vrFYUzTXMJpMBU+OA292I6IUxKWCCfZkgXg+/kBZMo=
|
||||
github.com/creachadair/mds v0.20.0 h1:bXQO154c2TDgCY+rRmdIfUqjeqGYejmZ/QayeTNwbp8=
|
||||
github.com/creachadair/mds v0.20.0/go.mod h1:4b//mUiL8YldH6TImXjmW45myzTLNS1LLjOmrk888eg=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
|
||||
@@ -134,10 +134,10 @@ github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yez
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ=
|
||||
github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
|
||||
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/cli v27.4.0+incompatible h1:/nJzWkcI1MDMN+U+px/YXnQWJqnu4J+QKGTfD6ptiTc=
|
||||
github.com/docker/cli v27.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v27.4.0+incompatible h1:I9z7sQ5qyzO0BfAb9IMOawRkAGxhYsidKiTMcm0DU+A=
|
||||
github.com/docker/docker v27.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
@@ -155,8 +155,6 @@ github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
|
||||
github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
|
||||
@@ -169,8 +167,8 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.2 h1:F/d1hpHbRAvKezziV2CC5KUE82cVe9zTgHSBoOOZ4CY=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.2/go.mod h1:9nHVX6z3FCMCQPA7PThGcA55t22yKQfK/Dnsf5i7hUo=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3 h1:ei3Vq/rpPI/jCJY9mRHJAKg5vU+EhZyWhBAkaAomQuw=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3/go.mod h1:VJ9FIOBAur+NmQ8c4tDVwOuiJcgupTG105FexPFrXzA=
|
||||
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
|
||||
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
|
||||
@@ -179,6 +177,10 @@ github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f
|
||||
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
@@ -224,8 +226,8 @@ github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdF
|
||||
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20240829160300-da1f7e9f2b25 h1:sEDPKUw6iPjczdu33njxFjO6tYa9bfc0z/QyB/zSsBw=
|
||||
github.com/google/pprof v0.0.0-20240829160300-da1f7e9f2b25/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
@@ -242,8 +244,8 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI=
|
||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
@@ -264,10 +266,10 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
||||
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jagottsicher/termcolor v1.0.2 h1:fo0c51pQSuLBN1+yVX2ZE+hE+P7ULb/TY8eRowJnrsM=
|
||||
github.com/jagottsicher/termcolor v1.0.2/go.mod h1:RcH8uFwF/0wbEdQmi83rjmlJ+QOKdMSE9Rc1BEB7zFo=
|
||||
github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g=
|
||||
@@ -290,8 +292,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
@@ -363,16 +365,17 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/opencontainers/runc v1.2.2 h1:jTg3Vw2A5f0N9PoxFTEwUhvpANGaNPT3689Yfd/zaX0=
|
||||
github.com/opencontainers/runc v1.2.2/go.mod h1:/PXzF0h531HTMsYQnmxXkBD7YaGShm/2zcRB79dksUc=
|
||||
github.com/opencontainers/runc v1.2.3 h1:fxE7amCzfZflJO2lHXf4y/y8M1BoAqp+FVmG19oYB80=
|
||||
github.com/opencontainers/runc v1.2.3/go.mod h1:nSxcWUydXrsBZVYNSkTjoQ/N6rcyTtn+1SD5D4+kRIM=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA=
|
||||
github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw=
|
||||
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/petermattis/goid v0.0.0-20241211131331-93ee7e083c43 h1:ah1dvbqPMN5+ocrg/ZSgZ6k8bOk+kcZQ7fnyx6UvOm4=
|
||||
github.com/petermattis/goid v0.0.0-20241211131331-93ee7e083c43/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
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=
|
||||
@@ -391,13 +394,13 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
|
||||
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
|
||||
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
|
||||
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.58.0 h1:N+N8vY4/23r6iYfD3UQZUoJPnUYAo7v6LG5XZxjZTXo=
|
||||
github.com/prometheus/common v0.58.0/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0=
|
||||
github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ=
|
||||
github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
|
||||
@@ -407,8 +410,8 @@ github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEej
|
||||
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
|
||||
github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
|
||||
github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
|
||||
github.com/pterm/pterm v0.12.79 h1:lH3yrYMhdpeqX9y5Ep1u7DejyHy7NSQg9qrBjF9dFT4=
|
||||
github.com/pterm/pterm v0.12.79/go.mod h1:1v/gzOF1N0FsjbgTHZ1wVycRkKiatFvJSJC4IGaQAAo=
|
||||
github.com/pterm/pterm v0.12.80 h1:mM55B+GnKUnLMUSqhdINe4s6tOuVQIetQ3my8JGyAIg=
|
||||
github.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaKGQlHo=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
@@ -417,8 +420,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
@@ -461,8 +464,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
|
||||
@@ -473,24 +476,24 @@ github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGg
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ=
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
|
||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
|
||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
|
||||
github.com/tailscale/hujson v0.0.0-20241010212012-29efb4a0184b h1:MNaGusDfB1qxEsl6iVb33Gbe777IKzPP5PDta0xGC8M=
|
||||
github.com/tailscale/hujson v0.0.0-20241010212012-29efb4a0184b/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo=
|
||||
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU=
|
||||
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
|
||||
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w=
|
||||
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU=
|
||||
github.com/tailscale/setec v0.0.0-20240314234648-9da8e7407257 h1:6WsbDYsikRNmmbfZoRoyIEA9tfl0aspPAE0t7nBj2B4=
|
||||
github.com/tailscale/setec v0.0.0-20240314234648-9da8e7407257/go.mod h1:hrq01/0LUDZf4mMkcZ7Ovmy33jvCi4RpESpb9kPxV6E=
|
||||
github.com/tailscale/squibble v0.0.0-20240418235321-9ee0eeb78185 h1:zT+qB+2Ghulj50d5Wq6h6vQYqD2sPdhy4FF6+FHedVE=
|
||||
github.com/tailscale/squibble v0.0.0-20240418235321-9ee0eeb78185/go.mod h1:LoIjI6z/6efr9ebISQ5l2vjQmjc8QJrAYZdy3Ec3sVs=
|
||||
github.com/tailscale/tailsql v0.0.0-20240418235827-820559f382c1 h1:wmsnxEEuRlgK7Bhdkmm0JGrjjc0JoHZThLLo0WXXbLs=
|
||||
github.com/tailscale/tailsql v0.0.0-20240418235827-820559f382c1/go.mod h1:XN193fbz9RR/5stlWPMMIZR+TTa1BUkDJm5Azwzxwgw=
|
||||
github.com/tailscale/setec v0.0.0-20240930150730-e6eb93658ed3 h1:Zk341hE1rcVUcDwA9XKmed2acHGGlbeFQzje6gvkuFo=
|
||||
github.com/tailscale/setec v0.0.0-20240930150730-e6eb93658ed3/go.mod h1:nexjfRM8veJVJ5PTbqYI2YrUj/jbk3deffEHO3DH9Q4=
|
||||
github.com/tailscale/squibble v0.0.0-20240909231413-32a80b9743f7 h1:nfklwaP8uNz2IbUygSKOQ1aDzzRRRLaIbPpnQWUUMGc=
|
||||
github.com/tailscale/squibble v0.0.0-20240909231413-32a80b9743f7/go.mod h1:YH/J7n7jNZOq10nTxxPANv2ha/Eg47/6J5b7NnOYAhQ=
|
||||
github.com/tailscale/tailsql v0.0.0-20241211062219-bf96884c6a49 h1:QFXXdoiYFiUS7a6DH7zE6Uacz3wMzH/1/VvWLnR9To4=
|
||||
github.com/tailscale/tailsql v0.0.0-20241211062219-bf96884c6a49/go.mod h1:IX3F8T6iILmg94hZGkkOf6rmjIHJCXNVqxOpiSUwHQQ=
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g=
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
|
||||
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
|
||||
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc h1:cezaQN9pvKVaw56Ma5qr/G646uKIYP0yQf+OyWN/okc=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20241113014420-4e883d38c8d3 h1:dmoPb3dG27tZgMtrvqfD/LW4w7gA6BSWl8prCPNmkCQ=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20241113014420-4e883d38c8d3/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
|
||||
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek=
|
||||
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
|
||||
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
||||
@@ -523,6 +526,16 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJu
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
|
||||
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
|
||||
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
|
||||
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
||||
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
|
||||
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
|
||||
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
||||
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
@@ -538,13 +551,13 @@ 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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
||||
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/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4=
|
||||
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -555,8 +568,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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
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=
|
||||
@@ -569,11 +582,11 @@ 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.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
|
||||
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -582,8 +595,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -624,8 +637,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.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
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=
|
||||
@@ -633,8 +646,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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
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=
|
||||
@@ -648,8 +661,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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||
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=
|
||||
@@ -663,19 +676,19 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241216192217-9240e9c98484 h1:ChAdCYNQFDk5fYvFZMywKLIijG7TC2m1C2CMEu11G3o=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241216192217-9240e9c98484/go.mod h1:KRUmxRI4JmbpAm8gcZM4Jsffi859fo5LQjILwuqj9z8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 h1:Z7FRVJPSMaHQxD0uXU8WdgFh8PseLM8Q8NzhnpMrBhQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
|
||||
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/grpc v1.69.0 h1:quSiOM1GJPmPH5XtU+BCoVXcDVJJAzNcoyfC2cCjGkI=
|
||||
google.golang.org/grpc v1.69.0/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
|
||||
google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -690,10 +703,10 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
||||
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
||||
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
|
||||
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 h1:TU8z2Lh3Bbq77w0t1eG8yRlLcNHzZu3x6mhoH2Mk0c8=
|
||||
@@ -732,8 +745,8 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||
tailscale.com v1.75.0-pre.0.20240926101731-7d1160ddaab7 h1:nfRWV6ECxwNvvXKtbqSVstjlEi1BWktzv3FuxWpyyx0=
|
||||
tailscale.com v1.75.0-pre.0.20240926101731-7d1160ddaab7/go.mod h1:xKxYf3B3PuezFlRaMT+VhuVu8XTFUTLy+VCzLPMJVmg=
|
||||
tailscale.com v1.79.0-pre h1:iJ4+ox4kxadiTJRlybF+9Co+CEDIa1dflMPuxUb5gRg=
|
||||
tailscale.com v1.79.0-pre/go.mod h1:aNv7W0AEQtUsDOByv8mGZAk5ZGT49gQ3vIaPaol1RCc=
|
||||
zgo.at/zcache/v2 v2.1.0 h1:USo+ubK+R4vtjw4viGzTe/zjXyPw6R7SK/RL3epBBxs=
|
||||
zgo.at/zcache/v2 v2.1.0/go.mod h1:gyCeoLVo01QjDZynjime8xUGHHMbsLiPyUTBpDGd4Gk=
|
||||
zombiezen.com/go/postgrestest v1.0.1 h1:aXoADQAJmZDU3+xilYVut0pHhgc0sF8ZspPW9gFNwP4=
|
||||
|
@@ -838,6 +838,10 @@ func (h *Headscale) Serve() error {
|
||||
Str("signal", sig.String()).
|
||||
Msg("Received SIGHUP, reloading ACL and Config")
|
||||
|
||||
if h.cfg.Policy.IsEmpty() {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := h.loadPolicyManager(); err != nil {
|
||||
log.Error().Err(err).Msg("failed to reload Policy")
|
||||
}
|
||||
@@ -1102,6 +1106,10 @@ func (h *Headscale) policyBytes() ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.Data == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return []byte(p.Data), err
|
||||
}
|
||||
|
||||
|
@@ -278,9 +278,9 @@ func TestConstraints(t *testing.T) {
|
||||
{
|
||||
name: "no-duplicate-username-if-no-oidc",
|
||||
run: func(t *testing.T, db *gorm.DB) {
|
||||
_, err := CreateUser(db, "user1")
|
||||
_, err := CreateUser(db, types.User{Name: "user1"})
|
||||
require.NoError(t, err)
|
||||
_, err = CreateUser(db, "user1")
|
||||
_, err = CreateUser(db, types.User{Name: "user1"})
|
||||
requireConstraintFailed(t, err)
|
||||
},
|
||||
},
|
||||
@@ -331,7 +331,7 @@ func TestConstraints(t *testing.T) {
|
||||
{
|
||||
name: "allow-duplicate-username-cli-then-oidc",
|
||||
run: func(t *testing.T, db *gorm.DB) {
|
||||
_, err := CreateUser(db, "user1") // Create CLI username
|
||||
_, err := CreateUser(db, types.User{Name: "user1"}) // Create CLI username
|
||||
require.NoError(t, err)
|
||||
|
||||
user := types.User{
|
||||
@@ -354,7 +354,7 @@ func TestConstraints(t *testing.T) {
|
||||
err := db.Save(&user).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = CreateUser(db, "user1") // Create CLI username
|
||||
_, err = CreateUser(db, types.User{Name: "user1"}) // Create CLI username
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
|
@@ -245,7 +245,7 @@ func RenameNode(tx *gorm.DB,
|
||||
return fmt.Errorf("renaming node: %w", err)
|
||||
}
|
||||
|
||||
uniq, err := isUnqiueName(tx, newName)
|
||||
uniq, err := isUniqueName(tx, newName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking if name is unique: %w", err)
|
||||
}
|
||||
@@ -630,7 +630,7 @@ func generateGivenName(suppliedName string, randomSuffix bool) (string, error) {
|
||||
return suppliedName, nil
|
||||
}
|
||||
|
||||
func isUnqiueName(tx *gorm.DB, name string) (bool, error) {
|
||||
func isUniqueName(tx *gorm.DB, name string) (bool, error) {
|
||||
nodes := types.Nodes{}
|
||||
if err := tx.
|
||||
Where("given_name = ?", name).Find(&nodes).Error; err != nil {
|
||||
@@ -649,7 +649,7 @@ func ensureUniqueGivenName(
|
||||
return "", err
|
||||
}
|
||||
|
||||
unique, err := isUnqiueName(tx, givenName)
|
||||
unique, err := isUniqueName(tx, givenName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ import (
|
||||
)
|
||||
|
||||
func (s *Suite) TestGetNode(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -56,7 +56,7 @@ func (s *Suite) TestGetNode(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetNodeByID(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -85,7 +85,7 @@ func (s *Suite) TestGetNodeByID(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetNodeByAnyNodeKey(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -116,7 +116,7 @@ func (s *Suite) TestGetNodeByAnyNodeKey(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *Suite) TestHardDeleteNode(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
nodeKey := key.NewNode()
|
||||
@@ -141,7 +141,7 @@ func (s *Suite) TestHardDeleteNode(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *Suite) TestListPeers(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -188,7 +188,7 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
||||
stor := make([]base, 0)
|
||||
|
||||
for _, name := range []string{"test", "admin"} {
|
||||
user, err := db.CreateUser(name)
|
||||
user, err := db.CreateUser(types.User{Name: name})
|
||||
c.Assert(err, check.IsNil)
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
@@ -279,7 +279,7 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *Suite) TestExpireNode(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -320,7 +320,7 @@ func (s *Suite) TestExpireNode(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *Suite) TestSetTags(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -565,7 +565,7 @@ func TestAutoApproveRoutes(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pol)
|
||||
|
||||
user, err := adb.CreateUser("test")
|
||||
user, err := adb.CreateUser(types.User{Name: "test"})
|
||||
require.NoError(t, err)
|
||||
|
||||
pak, err := adb.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -706,7 +706,7 @@ func TestListEphemeralNodes(t *testing.T) {
|
||||
t.Fatalf("creating db: %s", err)
|
||||
}
|
||||
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
require.NoError(t, err)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -762,10 +762,10 @@ func TestRenameNode(t *testing.T) {
|
||||
t.Fatalf("creating db: %s", err)
|
||||
}
|
||||
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
require.NoError(t, err)
|
||||
|
||||
user2, err := db.CreateUser("test2")
|
||||
user2, err := db.CreateUser(types.User{Name: "user2"})
|
||||
require.NoError(t, err)
|
||||
|
||||
node := types.Node{
|
||||
|
@@ -15,7 +15,7 @@ func (*Suite) TestCreatePreAuthKey(c *check.C) {
|
||||
_, err := db.CreatePreAuthKey(12345, true, false, nil, nil)
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
key, err := db.CreatePreAuthKey(types.UserID(user.ID), true, false, nil, nil)
|
||||
@@ -41,7 +41,7 @@ func (*Suite) TestCreatePreAuthKey(c *check.C) {
|
||||
}
|
||||
|
||||
func (*Suite) TestExpiredPreAuthKey(c *check.C) {
|
||||
user, err := db.CreateUser("test2")
|
||||
user, err := db.CreateUser(types.User{Name: "test2"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
now := time.Now().Add(-5 * time.Second)
|
||||
@@ -60,7 +60,7 @@ func (*Suite) TestPreAuthKeyDoesNotExist(c *check.C) {
|
||||
}
|
||||
|
||||
func (*Suite) TestValidateKeyOk(c *check.C) {
|
||||
user, err := db.CreateUser("test3")
|
||||
user, err := db.CreateUser(types.User{Name: "test3"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), true, false, nil, nil)
|
||||
@@ -72,7 +72,7 @@ func (*Suite) TestValidateKeyOk(c *check.C) {
|
||||
}
|
||||
|
||||
func (*Suite) TestAlreadyUsedKey(c *check.C) {
|
||||
user, err := db.CreateUser("test4")
|
||||
user, err := db.CreateUser(types.User{Name: "test4"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -94,7 +94,7 @@ func (*Suite) TestAlreadyUsedKey(c *check.C) {
|
||||
}
|
||||
|
||||
func (*Suite) TestReusableBeingUsedKey(c *check.C) {
|
||||
user, err := db.CreateUser("test5")
|
||||
user, err := db.CreateUser(types.User{Name: "test5"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), true, false, nil, nil)
|
||||
@@ -116,7 +116,7 @@ func (*Suite) TestReusableBeingUsedKey(c *check.C) {
|
||||
}
|
||||
|
||||
func (*Suite) TestNotReusableNotBeingUsedKey(c *check.C) {
|
||||
user, err := db.CreateUser("test6")
|
||||
user, err := db.CreateUser(types.User{Name: "test6"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -128,7 +128,7 @@ func (*Suite) TestNotReusableNotBeingUsedKey(c *check.C) {
|
||||
}
|
||||
|
||||
func (*Suite) TestExpirePreauthKey(c *check.C) {
|
||||
user, err := db.CreateUser("test3")
|
||||
user, err := db.CreateUser(types.User{Name: "test3"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), true, false, nil, nil)
|
||||
@@ -145,7 +145,7 @@ func (*Suite) TestExpirePreauthKey(c *check.C) {
|
||||
}
|
||||
|
||||
func (*Suite) TestNotReusableMarkedAsUsed(c *check.C) {
|
||||
user, err := db.CreateUser("test6")
|
||||
user, err := db.CreateUser(types.User{Name: "test6"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -158,7 +158,7 @@ func (*Suite) TestNotReusableMarkedAsUsed(c *check.C) {
|
||||
}
|
||||
|
||||
func (*Suite) TestPreAuthKeyACLTags(c *check.C) {
|
||||
user, err := db.CreateUser("test8")
|
||||
user, err := db.CreateUser(types.User{Name: "test8"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, []string{"badtag"})
|
||||
|
@@ -417,10 +417,10 @@ func SaveNodeRoutes(tx *gorm.DB, node *types.Node) (bool, error) {
|
||||
return sendUpdate, nil
|
||||
}
|
||||
|
||||
// FailoverNodeRoutesIfNeccessary takes a node and checks if the node's route
|
||||
// FailoverNodeRoutesIfNecessary takes a node and checks if the node's route
|
||||
// need to be failed over to another host.
|
||||
// If needed, the failover will be attempted.
|
||||
func FailoverNodeRoutesIfNeccessary(
|
||||
func FailoverNodeRoutesIfNecessary(
|
||||
tx *gorm.DB,
|
||||
isLikelyConnected *xsync.MapOf[types.NodeID, bool],
|
||||
node *types.Node,
|
||||
@@ -473,7 +473,7 @@ nodeRouteLoop:
|
||||
return &types.StateUpdate{
|
||||
Type: types.StatePeerChanged,
|
||||
ChangeNodes: chng,
|
||||
Message: "called from db.FailoverNodeRoutesIfNeccessary",
|
||||
Message: "called from db.FailoverNodeRoutesIfNecessary",
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@@ -32,7 +32,7 @@ var mp = func(p string) netip.Prefix {
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetRoutes(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -76,7 +76,7 @@ func (s *Suite) TestGetRoutes(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetEnableRoutes(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -150,7 +150,7 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *Suite) TestIsUniquePrefix(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -231,7 +231,7 @@ func (s *Suite) TestIsUniquePrefix(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *Suite) TestDeleteRoutes(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -342,7 +342,7 @@ func dbForTest(t *testing.T, testName string) *HSDatabase {
|
||||
return db
|
||||
}
|
||||
|
||||
func TestFailoverNodeRoutesIfNeccessary(t *testing.T) {
|
||||
func TestFailoverNodeRoutesIfNecessary(t *testing.T) {
|
||||
su := func(nids ...types.NodeID) *types.StateUpdate {
|
||||
return &types.StateUpdate{
|
||||
ChangeNodes: nids,
|
||||
@@ -648,7 +648,7 @@ func TestFailoverNodeRoutesIfNeccessary(t *testing.T) {
|
||||
want := tt.want[step]
|
||||
|
||||
got, err := Write(db.DB, func(tx *gorm.DB) (*types.StateUpdate, error) {
|
||||
return FailoverNodeRoutesIfNeccessary(tx, smap(isConnected), node)
|
||||
return FailoverNodeRoutesIfNecessary(tx, smap(isConnected), node)
|
||||
})
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
|
@@ -15,22 +15,19 @@ var (
|
||||
ErrUserStillHasNodes = errors.New("user not empty: node(s) found")
|
||||
)
|
||||
|
||||
func (hsdb *HSDatabase) CreateUser(name string) (*types.User, error) {
|
||||
func (hsdb *HSDatabase) CreateUser(user types.User) (*types.User, error) {
|
||||
return Write(hsdb.DB, func(tx *gorm.DB) (*types.User, error) {
|
||||
return CreateUser(tx, name)
|
||||
return CreateUser(tx, user)
|
||||
})
|
||||
}
|
||||
|
||||
// CreateUser creates a new User. Returns error if could not be created
|
||||
// or another user already exists.
|
||||
func CreateUser(tx *gorm.DB, name string) (*types.User, error) {
|
||||
err := util.CheckForFQDNRules(name)
|
||||
func CreateUser(tx *gorm.DB, user types.User) (*types.User, error) {
|
||||
err := util.CheckForFQDNRules(user.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := types.User{
|
||||
Name: name,
|
||||
}
|
||||
if err := tx.Create(&user).Error; err != nil {
|
||||
return nil, fmt.Errorf("creating user: %w", err)
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func (s *Suite) TestCreateAndDestroyUser(c *check.C) {
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(user.Name, check.Equals, "test")
|
||||
|
||||
@@ -30,7 +30,7 @@ func (s *Suite) TestDestroyUserErrors(c *check.C) {
|
||||
err := db.DestroyUser(9998)
|
||||
c.Assert(err, check.Equals, ErrUserNotFound)
|
||||
|
||||
user, err := db.CreateUser("test")
|
||||
user, err := db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -43,7 +43,7 @@ func (s *Suite) TestDestroyUserErrors(c *check.C) {
|
||||
// destroying a user also deletes all associated preauthkeys
|
||||
c.Assert(result.Error, check.Equals, gorm.ErrRecordNotFound)
|
||||
|
||||
user, err = db.CreateUser("test")
|
||||
user, err = db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err = db.CreatePreAuthKey(types.UserID(user.ID), false, false, nil, nil)
|
||||
@@ -64,7 +64,7 @@ func (s *Suite) TestDestroyUserErrors(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *Suite) TestRenameUser(c *check.C) {
|
||||
userTest, err := db.CreateUser("test")
|
||||
userTest, err := db.CreateUser(types.User{Name: "test"})
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(userTest.Name, check.Equals, "test")
|
||||
|
||||
@@ -86,7 +86,7 @@ func (s *Suite) TestRenameUser(c *check.C) {
|
||||
err = db.RenameUser(99988, "test")
|
||||
c.Assert(err, check.Equals, ErrUserNotFound)
|
||||
|
||||
userTest2, err := db.CreateUser("test2")
|
||||
userTest2, err := db.CreateUser(types.User{Name: "test2"})
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(userTest2.Name, check.Equals, "test2")
|
||||
|
||||
@@ -98,10 +98,10 @@ func (s *Suite) TestRenameUser(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *Suite) TestSetMachineUser(c *check.C) {
|
||||
oldUser, err := db.CreateUser("old")
|
||||
oldUser, err := db.CreateUser(types.User{Name: "old"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
newUser, err := db.CreateUser("new")
|
||||
newUser, err := db.CreateUser(types.User{Name: "new"})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := db.CreatePreAuthKey(types.UserID(oldUser.ID), false, false, nil, nil)
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/rs/zerolog/log"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -83,13 +84,40 @@ func (e *ExtraRecordsMan) Run() {
|
||||
log.Error().Caller().Msgf("file watcher event channel closing")
|
||||
return
|
||||
}
|
||||
|
||||
switch event.Op {
|
||||
case fsnotify.Create, fsnotify.Write, fsnotify.Chmod:
|
||||
log.Trace().Caller().Str("path", event.Name).Str("op", event.Op.String()).Msg("extra records received filewatch event")
|
||||
if event.Name != e.path {
|
||||
continue
|
||||
}
|
||||
e.updateRecords()
|
||||
|
||||
// If a file is removed or renamed, fsnotify will loose track of it
|
||||
// and not watch it. We will therefore attempt to re-add it with a backoff.
|
||||
case fsnotify.Remove, fsnotify.Rename:
|
||||
err := backoff.Retry(func() error {
|
||||
if _, err := os.Stat(e.path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}, backoff.NewExponentialBackOff())
|
||||
|
||||
if err != nil {
|
||||
log.Error().Caller().Err(err).Msgf("extra records filewatcher retrying to find file after delete")
|
||||
continue
|
||||
}
|
||||
|
||||
err = e.watcher.Add(e.path)
|
||||
if err != nil {
|
||||
log.Error().Caller().Err(err).Msgf("extra records filewatcher re-adding file after delete failed, giving up.")
|
||||
return
|
||||
} else {
|
||||
log.Trace().Caller().Str("path", e.path).Msg("extra records file re-added after delete")
|
||||
e.updateRecords()
|
||||
}
|
||||
}
|
||||
|
||||
case err, ok := <-e.watcher.Errors:
|
||||
if !ok {
|
||||
log.Error().Caller().Msgf("file watcher error channel closing")
|
||||
@@ -116,6 +144,11 @@ func (e *ExtraRecordsMan) updateRecords() {
|
||||
return
|
||||
}
|
||||
|
||||
// If there are no records, ignore the update.
|
||||
if records == nil {
|
||||
return
|
||||
}
|
||||
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
@@ -143,6 +176,12 @@ func readExtraRecordsFromPath(path string) ([]tailcfg.DNSRecord, [32]byte, error
|
||||
return nil, [32]byte{}, fmt.Errorf("reading path: %s, err: %w", path, err)
|
||||
}
|
||||
|
||||
// If the read was triggered too fast, and the file is not complete, ignore the update
|
||||
// if the file is empty. A consecutive update will be triggered when the file is complete.
|
||||
if len(b) == 0 {
|
||||
return nil, [32]byte{}, nil
|
||||
}
|
||||
|
||||
var records []tailcfg.DNSRecord
|
||||
err = json.Unmarshal(b, &records)
|
||||
if err != nil {
|
||||
|
@@ -11,7 +11,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/samber/lo"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
@@ -21,6 +23,7 @@ import (
|
||||
|
||||
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"
|
||||
)
|
||||
@@ -40,7 +43,13 @@ func (api headscaleV1APIServer) CreateUser(
|
||||
ctx context.Context,
|
||||
request *v1.CreateUserRequest,
|
||||
) (*v1.CreateUserResponse, error) {
|
||||
user, err := api.h.db.CreateUser(request.GetName())
|
||||
newUser := types.User{
|
||||
Name: request.GetName(),
|
||||
DisplayName: request.GetDisplayName(),
|
||||
Email: request.GetEmail(),
|
||||
ProfilePicURL: request.GetPictureUrl(),
|
||||
}
|
||||
user, err := api.h.db.CreateUser(newUser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -457,19 +466,7 @@ func (api headscaleV1APIServer) ListNodes(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := make([]*v1.Node, len(nodes))
|
||||
for index, node := range nodes {
|
||||
resp := node.Proto()
|
||||
|
||||
// Populate the online field based on
|
||||
// currently connected nodes.
|
||||
if val, ok := isLikelyConnected.Load(node.ID); ok && val {
|
||||
resp.Online = true
|
||||
}
|
||||
|
||||
response[index] = resp
|
||||
}
|
||||
|
||||
response := nodesToProto(api.h.polMan, isLikelyConnected, nodes)
|
||||
return &v1.ListNodesResponse{Nodes: response}, nil
|
||||
}
|
||||
|
||||
@@ -482,6 +479,11 @@ func (api headscaleV1APIServer) ListNodes(
|
||||
return nodes[i].ID < nodes[j].ID
|
||||
})
|
||||
|
||||
response := nodesToProto(api.h.polMan, isLikelyConnected, nodes)
|
||||
return &v1.ListNodesResponse{Nodes: response}, nil
|
||||
}
|
||||
|
||||
func nodesToProto(polMan policy.PolicyManager, isLikelyConnected *xsync.MapOf[types.NodeID, bool], nodes types.Nodes) []*v1.Node {
|
||||
response := make([]*v1.Node, len(nodes))
|
||||
for index, node := range nodes {
|
||||
resp := node.Proto()
|
||||
@@ -492,12 +494,12 @@ func (api headscaleV1APIServer) ListNodes(
|
||||
resp.Online = true
|
||||
}
|
||||
|
||||
validTags := api.h.polMan.Tags(node)
|
||||
resp.ValidTags = validTags
|
||||
tags := polMan.Tags(node)
|
||||
resp.ValidTags = lo.Uniq(append(tags, node.ForcedTags...))
|
||||
response[index] = resp
|
||||
}
|
||||
|
||||
return &v1.ListNodesResponse{Nodes: response}, nil
|
||||
return response
|
||||
}
|
||||
|
||||
func (api headscaleV1APIServer) MoveNode(
|
||||
|
@@ -105,8 +105,7 @@ func generateUserProfiles(
|
||||
|
||||
var profiles []tailcfg.UserProfile
|
||||
for _, user := range userMap {
|
||||
profiles = append(profiles,
|
||||
user.TailscaleUserProfile())
|
||||
profiles = append(profiles, user.TailscaleUserProfile())
|
||||
}
|
||||
|
||||
return profiles
|
||||
@@ -455,7 +454,7 @@ func (m *Mapper) baseWithConfigMapResponse(
|
||||
|
||||
resp.DERPMap = m.derpMap
|
||||
|
||||
resp.Domain = m.cfg.BaseDomain
|
||||
resp.Domain = m.cfg.Domain()
|
||||
|
||||
// Do not instruct clients to collect services we do not
|
||||
// support or do anything with them
|
||||
|
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/net/http2"
|
||||
"tailscale.com/control/controlbase"
|
||||
"tailscale.com/control/controlhttp"
|
||||
"tailscale.com/control/controlhttp/controlhttpserver"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
@@ -71,7 +71,7 @@ func (h *Headscale) NoiseUpgradeHandler(
|
||||
challenge: key.NewChallenge(),
|
||||
}
|
||||
|
||||
noiseConn, err := controlhttp.AcceptHTTP(
|
||||
noiseConn, err := controlhttpserver.AcceptHTTP(
|
||||
req.Context(),
|
||||
writer,
|
||||
req,
|
||||
|
@@ -243,7 +243,7 @@ func (n *Notifier) sendAll(update types.StateUpdate) {
|
||||
// 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
|
||||
// next node if the context is cancelled. After 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()
|
||||
|
@@ -3,9 +3,7 @@ package hscontrol
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
_ "embed"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
@@ -29,11 +27,13 @@ import (
|
||||
|
||||
const (
|
||||
randomByteSize = 16
|
||||
defaultOAuthOptionsCount = 3
|
||||
)
|
||||
|
||||
var (
|
||||
errEmptyOIDCCallbackParams = errors.New("empty OIDC callback params")
|
||||
errNoOIDCIDToken = errors.New("could not extract ID Token for OIDC callback")
|
||||
errNoOIDCRegistrationInfo = errors.New("could not get registration info from cache")
|
||||
errOIDCAllowedDomains = errors.New(
|
||||
"authenticated principal does not match any allowed domain",
|
||||
)
|
||||
@@ -47,11 +47,17 @@ var (
|
||||
errOIDCNodeKeyMissing = errors.New("could not get node key from cache")
|
||||
)
|
||||
|
||||
// RegistrationInfo contains both machine key and verifier information for OIDC validation.
|
||||
type RegistrationInfo struct {
|
||||
MachineKey key.MachinePublic
|
||||
Verifier *string
|
||||
}
|
||||
|
||||
type AuthProviderOIDC struct {
|
||||
serverURL string
|
||||
cfg *types.OIDCConfig
|
||||
db *db.HSDatabase
|
||||
registrationCache *zcache.Cache[string, key.MachinePublic]
|
||||
registrationCache *zcache.Cache[string, RegistrationInfo]
|
||||
notifier *notifier.Notifier
|
||||
ipAlloc *db.IPAllocator
|
||||
polMan policy.PolicyManager
|
||||
@@ -87,7 +93,7 @@ func NewAuthProviderOIDC(
|
||||
Scopes: cfg.Scope,
|
||||
}
|
||||
|
||||
registrationCache := zcache.New[string, key.MachinePublic](
|
||||
registrationCache := zcache.New[string, RegistrationInfo](
|
||||
registerCacheExpiration,
|
||||
registerCacheCleanup,
|
||||
)
|
||||
@@ -149,28 +155,52 @@ func (a *AuthProviderOIDC) RegisterHandler(
|
||||
return
|
||||
}
|
||||
|
||||
randomBlob := make([]byte, randomByteSize)
|
||||
if _, err := rand.Read(randomBlob); err != nil {
|
||||
// Set the state and nonce cookies to protect against CSRF attacks
|
||||
state, err := setCSRFCookie(writer, req, "state")
|
||||
if err != nil {
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
stateStr := hex.EncodeToString(randomBlob)[:32]
|
||||
// Set the state and nonce cookies to protect against CSRF attacks
|
||||
nonce, err := setCSRFCookie(writer, req, "nonce")
|
||||
if err != nil {
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// place the node key into the state cache, so it can be retrieved later
|
||||
a.registrationCache.Set(
|
||||
stateStr,
|
||||
machineKey,
|
||||
)
|
||||
// Initialize registration info with machine key
|
||||
registrationInfo := RegistrationInfo{
|
||||
MachineKey: machineKey,
|
||||
}
|
||||
|
||||
// Add any extra parameter provided in the configuration to the Authorize Endpoint request
|
||||
extras := make([]oauth2.AuthCodeOption, 0, len(a.cfg.ExtraParams))
|
||||
extras := make([]oauth2.AuthCodeOption, 0, len(a.cfg.ExtraParams)+defaultOAuthOptionsCount)
|
||||
// Add PKCE verification if enabled
|
||||
if a.cfg.PKCE.Enabled {
|
||||
verifier := oauth2.GenerateVerifier()
|
||||
registrationInfo.Verifier = &verifier
|
||||
|
||||
extras = append(extras, oauth2.AccessTypeOffline)
|
||||
|
||||
switch a.cfg.PKCE.Method {
|
||||
case types.PKCEMethodS256:
|
||||
extras = append(extras, oauth2.S256ChallengeOption(verifier))
|
||||
case types.PKCEMethodPlain:
|
||||
// oauth2 does not have a plain challenge option, so we add it manually
|
||||
extras = append(extras, oauth2.SetAuthURLParam("code_challenge_method", "plain"), oauth2.SetAuthURLParam("code_challenge", verifier))
|
||||
}
|
||||
}
|
||||
|
||||
// Add any extra parameters from configuration
|
||||
for k, v := range a.cfg.ExtraParams {
|
||||
extras = append(extras, oauth2.SetAuthURLParam(k, v))
|
||||
}
|
||||
extras = append(extras, oidc.Nonce(nonce))
|
||||
|
||||
authURL := a.oauth2Config.AuthCodeURL(stateStr, extras...)
|
||||
// Cache the registration info
|
||||
a.registrationCache.Set(state, registrationInfo)
|
||||
|
||||
authURL := a.oauth2Config.AuthCodeURL(state, extras...)
|
||||
log.Debug().Msgf("Redirecting to %s for authentication", authURL)
|
||||
|
||||
http.Redirect(writer, req, authURL, http.StatusFound)
|
||||
@@ -203,11 +233,34 @@ func (a *AuthProviderOIDC) OIDCCallbackHandler(
|
||||
return
|
||||
}
|
||||
|
||||
idToken, err := a.extractIDToken(req.Context(), code)
|
||||
log.Debug().Interface("cookies", req.Cookies()).Msg("Received oidc callback")
|
||||
cookieState, err := req.Cookie("state")
|
||||
if err != nil {
|
||||
http.Error(writer, "state not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if state != cookieState.Value {
|
||||
http.Error(writer, "state did not match", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
idToken, err := a.extractIDToken(req.Context(), code, state)
|
||||
if err != nil {
|
||||
http.Error(writer, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
nonce, err := req.Cookie("nonce")
|
||||
if err != nil {
|
||||
http.Error(writer, "nonce not found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if idToken.Nonce != nonce.Value {
|
||||
http.Error(writer, "nonce did not match", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
nodeExpiry := a.determineNodeExpiry(idToken.Expiry)
|
||||
|
||||
var claims types.OIDCClaims
|
||||
@@ -296,7 +349,7 @@ func (a *AuthProviderOIDC) OIDCCallbackHandler(
|
||||
|
||||
// Neither node nor machine key was found in the state cache meaning
|
||||
// that we could not reauth nor register the node.
|
||||
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||
http.Error(writer, "login session expired, try again", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -318,8 +371,21 @@ func extractCodeAndStateParamFromRequest(
|
||||
func (a *AuthProviderOIDC) extractIDToken(
|
||||
ctx context.Context,
|
||||
code string,
|
||||
state string,
|
||||
) (*oidc.IDToken, error) {
|
||||
oauth2Token, err := a.oauth2Config.Exchange(ctx, code)
|
||||
var exchangeOpts []oauth2.AuthCodeOption
|
||||
|
||||
if a.cfg.PKCE.Enabled {
|
||||
regInfo, ok := a.registrationCache.Get(state)
|
||||
if !ok {
|
||||
return nil, errNoOIDCRegistrationInfo
|
||||
}
|
||||
if regInfo.Verifier != nil {
|
||||
exchangeOpts = []oauth2.AuthCodeOption{oauth2.VerifierOption(*regInfo.Verifier)}
|
||||
}
|
||||
}
|
||||
|
||||
oauth2Token, err := a.oauth2Config.Exchange(ctx, code, exchangeOpts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not exchange code for token: %w", err)
|
||||
}
|
||||
@@ -394,7 +460,7 @@ func validateOIDCAllowedUsers(
|
||||
// cache. If the machine key is found, it will try retrieve the
|
||||
// node information from the database.
|
||||
func (a *AuthProviderOIDC) getMachineKeyFromState(state string) (*types.Node, *key.MachinePublic) {
|
||||
machineKey, ok := a.registrationCache.Get(state)
|
||||
regInfo, ok := a.registrationCache.Get(state)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -403,9 +469,9 @@ func (a *AuthProviderOIDC) getMachineKeyFromState(state string) (*types.Node, *k
|
||||
// The error is not important, because if it does not
|
||||
// exist, then this is a new node and we will move
|
||||
// on to registration.
|
||||
node, _ := a.db.GetNodeByMachineKey(machineKey)
|
||||
node, _ := a.db.GetNodeByMachineKey(regInfo.MachineKey)
|
||||
|
||||
return node, &machineKey
|
||||
return node, ®Info.MachineKey
|
||||
}
|
||||
|
||||
// reauthenticateNode updates the node expiry in the database
|
||||
@@ -554,3 +620,22 @@ func getUserName(
|
||||
|
||||
return userName, nil
|
||||
}
|
||||
|
||||
func setCSRFCookie(w http.ResponseWriter, r *http.Request, name string) (string, error) {
|
||||
val, err := util.GenerateRandomStringURLSafe(64)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
||||
c := &http.Cookie{
|
||||
Path: "/oidc/callback",
|
||||
Name: name,
|
||||
Value: val,
|
||||
MaxAge: int(time.Hour.Seconds()),
|
||||
Secure: r.TLS != nil,
|
||||
HttpOnly: true,
|
||||
}
|
||||
http.SetCookie(w, c)
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"iter"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
@@ -61,7 +62,7 @@ func theInternet() *netipx.IPSet {
|
||||
internetBuilder.RemovePrefix(tsaddr.CGNATRange())
|
||||
|
||||
// Delete "cant find DHCP networks"
|
||||
internetBuilder.RemovePrefix(netip.MustParsePrefix("fe80::/10")) // link-loca
|
||||
internetBuilder.RemovePrefix(netip.MustParsePrefix("fe80::/10")) // link-local
|
||||
internetBuilder.RemovePrefix(netip.MustParsePrefix("169.254.0.0/16"))
|
||||
|
||||
theInternetSet, _ := internetBuilder.IPSet()
|
||||
@@ -361,39 +362,69 @@ func (pol *ACLPolicy) CompileSSHPolicy(
|
||||
)
|
||||
}
|
||||
|
||||
principals := make([]*tailcfg.SSHPrincipal, 0, len(sshACL.Sources))
|
||||
for innerIndex, rawSrc := range sshACL.Sources {
|
||||
if isWildcard(rawSrc) {
|
||||
principals = append(principals, &tailcfg.SSHPrincipal{
|
||||
var principals []*tailcfg.SSHPrincipal
|
||||
for innerIndex, srcToken := range sshACL.Sources {
|
||||
if isWildcard(srcToken) {
|
||||
principals = []*tailcfg.SSHPrincipal{{
|
||||
Any: true,
|
||||
})
|
||||
} else if isGroup(rawSrc) {
|
||||
users, err := pol.expandUsersFromGroup(rawSrc)
|
||||
}}
|
||||
break
|
||||
}
|
||||
|
||||
// If the token is a group, expand the users and validate
|
||||
// them. Then use the .Username() to get the login name
|
||||
// that corresponds with the User info in the netmap.
|
||||
if isGroup(srcToken) {
|
||||
usersFromGroup, err := pol.expandUsersFromGroup(srcToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing SSH policy, expanding user from group, index: %d->%d: %w", index, innerIndex, err)
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
for _, userStr := range usersFromGroup {
|
||||
user, err := findUserFromTokenOrErr(users, userStr)
|
||||
if err != nil {
|
||||
log.Trace().Err(err).Msg("user not found")
|
||||
continue
|
||||
}
|
||||
|
||||
principals = append(principals, &tailcfg.SSHPrincipal{
|
||||
UserLogin: user,
|
||||
UserLogin: user.Username(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
expandedSrcs, err := pol.ExpandAlias(
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to check if the token is a user, if it is, then we
|
||||
// can use the .Username() to get the login name that
|
||||
// corresponds with the User info in the netmap.
|
||||
// TODO(kradalby): This is a bit of a hack, and it should go
|
||||
// away with the new policy where users can be reliably determined.
|
||||
if user, err := findUserFromTokenOrErr(users, srcToken); err == nil {
|
||||
principals = append(principals, &tailcfg.SSHPrincipal{
|
||||
UserLogin: user.Username(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// This is kind of then non-ideal scenario where we dont really know
|
||||
// what to do with the token, so we expand it to IP addresses of nodes.
|
||||
// The pro here is that we have a pretty good lockdown on the mapping
|
||||
// between users and node, but it can explode if a user owns many nodes.
|
||||
ips, err := pol.ExpandAlias(
|
||||
peers,
|
||||
users,
|
||||
rawSrc,
|
||||
srcToken,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing SSH policy, expanding alias, index: %d->%d: %w", index, innerIndex, err)
|
||||
}
|
||||
for _, expandedSrc := range expandedSrcs.Prefixes() {
|
||||
for addr := range ipSetAll(ips) {
|
||||
principals = append(principals, &tailcfg.SSHPrincipal{
|
||||
NodeIP: expandedSrc.Addr().String(),
|
||||
NodeIP: addr.String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userMap := make(map[string]string, len(sshACL.Users))
|
||||
for _, user := range sshACL.Users {
|
||||
@@ -411,6 +442,19 @@ func (pol *ACLPolicy) CompileSSHPolicy(
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ipSetAll returns a function that iterates over all the IPs in the IPSet.
|
||||
func ipSetAll(ipSet *netipx.IPSet) iter.Seq[netip.Addr] {
|
||||
return func(yield func(netip.Addr) bool) {
|
||||
for _, rng := range ipSet.Ranges() {
|
||||
for ip := rng.From(); ip.Compare(rng.To()) <= 0; ip = ip.Next() {
|
||||
if !yield(ip) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sshCheckAction(duration string) (*tailcfg.SSHAction, error) {
|
||||
sessionLength, err := time.ParseDuration(duration)
|
||||
if err != nil {
|
||||
@@ -934,6 +978,7 @@ func isAutoGroup(str string) bool {
|
||||
// Invalid tags are tags added by a user on a node, and that user doesn't have authority to add this tag.
|
||||
// Valid tags are tags added by a user that is allowed in the ACL policy to add this tag.
|
||||
func (pol *ACLPolicy) TagsOfNode(
|
||||
users []types.User,
|
||||
node *types.Node,
|
||||
) ([]string, []string) {
|
||||
var validTags []string
|
||||
@@ -956,7 +1001,12 @@ func (pol *ACLPolicy) TagsOfNode(
|
||||
}
|
||||
var found bool
|
||||
for _, owner := range owners {
|
||||
if node.User.Username() == owner {
|
||||
user, err := findUserFromTokenOrErr(users, owner)
|
||||
if err != nil {
|
||||
log.Trace().Caller().Err(err).Msg("could not determine user to filter tags by")
|
||||
}
|
||||
|
||||
if node.User.ID == user.ID {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
@@ -988,29 +1038,11 @@ func (pol *ACLPolicy) TagsOfNode(
|
||||
func filterNodesByUser(nodes types.Nodes, users []types.User, userToken string) types.Nodes {
|
||||
var out types.Nodes
|
||||
|
||||
var potentialUsers []types.User
|
||||
for _, user := range users {
|
||||
if user.ProviderIdentifier.Valid && user.ProviderIdentifier.String == userToken {
|
||||
// If a user is matching with a known unique field,
|
||||
// disgard all other users and only keep the current
|
||||
// user.
|
||||
potentialUsers = []types.User{user}
|
||||
|
||||
break
|
||||
user, err := findUserFromTokenOrErr(users, userToken)
|
||||
if err != nil {
|
||||
log.Trace().Caller().Err(err).Msg("could not determine user to filter nodes by")
|
||||
return out
|
||||
}
|
||||
if user.Email == userToken {
|
||||
potentialUsers = append(potentialUsers, user)
|
||||
}
|
||||
if user.Name == userToken {
|
||||
potentialUsers = append(potentialUsers, user)
|
||||
}
|
||||
}
|
||||
|
||||
if len(potentialUsers) != 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
user := potentialUsers[0]
|
||||
|
||||
for _, node := range nodes {
|
||||
if node.User.ID == user.ID {
|
||||
@@ -1021,6 +1053,44 @@ func filterNodesByUser(nodes types.Nodes, users []types.User, userToken string)
|
||||
return out
|
||||
}
|
||||
|
||||
var (
|
||||
ErrorNoUserMatching = errors.New("no user matching")
|
||||
ErrorMultipleUserMatching = errors.New("multiple users matching")
|
||||
)
|
||||
|
||||
func findUserFromTokenOrErr(
|
||||
users []types.User,
|
||||
token string,
|
||||
) (types.User, error) {
|
||||
var potentialUsers []types.User
|
||||
for _, user := range users {
|
||||
if user.ProviderIdentifier.Valid && user.ProviderIdentifier.String == token {
|
||||
// If a user is matching with a known unique field,
|
||||
// disgard all other users and only keep the current
|
||||
// user.
|
||||
potentialUsers = []types.User{user}
|
||||
|
||||
break
|
||||
}
|
||||
if user.Email == token {
|
||||
potentialUsers = append(potentialUsers, user)
|
||||
}
|
||||
if user.Name == token {
|
||||
potentialUsers = append(potentialUsers, user)
|
||||
}
|
||||
}
|
||||
|
||||
if len(potentialUsers) == 0 {
|
||||
return types.User{}, fmt.Errorf("user with token %q not found: %w", token, ErrorNoUserMatching)
|
||||
}
|
||||
|
||||
if len(potentialUsers) > 1 {
|
||||
return types.User{}, fmt.Errorf("multiple users with token %q found: %w", token, ErrorNoUserMatching)
|
||||
}
|
||||
|
||||
return potentialUsers[0], nil
|
||||
}
|
||||
|
||||
// FilterNodesByACL returns the list of peers authorized to be accessed from a given node.
|
||||
func FilterNodesByACL(
|
||||
node *types.Node,
|
||||
|
@@ -2735,6 +2735,12 @@ func TestReduceFilterRules(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_getTags(t *testing.T) {
|
||||
users := []types.User{
|
||||
{
|
||||
Model: gorm.Model{ID: 1},
|
||||
Name: "joe",
|
||||
},
|
||||
}
|
||||
type args struct {
|
||||
aclPolicy *ACLPolicy
|
||||
node *types.Node
|
||||
@@ -2754,9 +2760,7 @@ func Test_getTags(t *testing.T) {
|
||||
},
|
||||
},
|
||||
node: &types.Node{
|
||||
User: types.User{
|
||||
Name: "joe",
|
||||
},
|
||||
User: users[0],
|
||||
Hostinfo: &tailcfg.Hostinfo{
|
||||
RequestTags: []string{"tag:valid"},
|
||||
},
|
||||
@@ -2774,9 +2778,7 @@ func Test_getTags(t *testing.T) {
|
||||
},
|
||||
},
|
||||
node: &types.Node{
|
||||
User: types.User{
|
||||
Name: "joe",
|
||||
},
|
||||
User: users[0],
|
||||
Hostinfo: &tailcfg.Hostinfo{
|
||||
RequestTags: []string{"tag:valid", "tag:invalid"},
|
||||
},
|
||||
@@ -2794,9 +2796,7 @@ func Test_getTags(t *testing.T) {
|
||||
},
|
||||
},
|
||||
node: &types.Node{
|
||||
User: types.User{
|
||||
Name: "joe",
|
||||
},
|
||||
User: users[0],
|
||||
Hostinfo: &tailcfg.Hostinfo{
|
||||
RequestTags: []string{
|
||||
"tag:invalid",
|
||||
@@ -2818,9 +2818,7 @@ func Test_getTags(t *testing.T) {
|
||||
},
|
||||
},
|
||||
node: &types.Node{
|
||||
User: types.User{
|
||||
Name: "joe",
|
||||
},
|
||||
User: users[0],
|
||||
Hostinfo: &tailcfg.Hostinfo{
|
||||
RequestTags: []string{"tag:invalid", "very-invalid"},
|
||||
},
|
||||
@@ -2834,9 +2832,7 @@ func Test_getTags(t *testing.T) {
|
||||
args: args{
|
||||
aclPolicy: &ACLPolicy{},
|
||||
node: &types.Node{
|
||||
User: types.User{
|
||||
Name: "joe",
|
||||
},
|
||||
User: users[0],
|
||||
Hostinfo: &tailcfg.Hostinfo{
|
||||
RequestTags: []string{"tag:invalid", "very-invalid"},
|
||||
},
|
||||
@@ -2849,6 +2845,7 @@ func Test_getTags(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
gotValid, gotInvalid := test.args.aclPolicy.TagsOfNode(
|
||||
users,
|
||||
test.args.node,
|
||||
)
|
||||
for _, valid := range gotValid {
|
||||
@@ -3542,6 +3539,11 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSSHRules(t *testing.T) {
|
||||
users := []types.User{
|
||||
{
|
||||
Name: "user1",
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
node types.Node
|
||||
@@ -3555,18 +3557,14 @@ func TestSSHRules(t *testing.T) {
|
||||
Hostname: "testnodes",
|
||||
IPv4: iap("100.64.99.42"),
|
||||
UserID: 0,
|
||||
User: types.User{
|
||||
Name: "user1",
|
||||
},
|
||||
User: users[0],
|
||||
},
|
||||
peers: types.Nodes{
|
||||
&types.Node{
|
||||
Hostname: "testnodes2",
|
||||
IPv4: iap("100.64.0.1"),
|
||||
UserID: 0,
|
||||
User: types.User{
|
||||
Name: "user1",
|
||||
},
|
||||
User: users[0],
|
||||
},
|
||||
},
|
||||
pol: ACLPolicy{
|
||||
@@ -3679,18 +3677,14 @@ func TestSSHRules(t *testing.T) {
|
||||
Hostname: "testnodes",
|
||||
IPv4: iap("100.64.0.1"),
|
||||
UserID: 0,
|
||||
User: types.User{
|
||||
Name: "user1",
|
||||
},
|
||||
User: users[0],
|
||||
},
|
||||
peers: types.Nodes{
|
||||
&types.Node{
|
||||
Hostname: "testnodes2",
|
||||
IPv4: iap("100.64.99.42"),
|
||||
UserID: 0,
|
||||
User: types.User{
|
||||
Name: "user1",
|
||||
},
|
||||
User: users[0],
|
||||
},
|
||||
},
|
||||
pol: ACLPolicy{
|
||||
@@ -3728,7 +3722,7 @@ func TestSSHRules(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.pol.CompileSSHPolicy(&tt.node, []types.User{}, tt.peers)
|
||||
got, err := tt.pol.CompileSSHPolicy(&tt.node, users, tt.peers)
|
||||
require.NoError(t, err)
|
||||
|
||||
if diff := cmp.Diff(tt.want, got); diff != "" {
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/rs/zerolog/log"
|
||||
"go4.org/netipx"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/util/deephash"
|
||||
@@ -122,6 +123,10 @@ func (pm *PolicyManagerV1) SSHPolicy(node *types.Node) (*tailcfg.SSHPolicy, erro
|
||||
}
|
||||
|
||||
func (pm *PolicyManagerV1) SetPolicy(polB []byte) (bool, error) {
|
||||
if len(polB) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
pol, err := LoadACLPolicyFromBytes(polB)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("parsing policy: %w", err)
|
||||
@@ -157,7 +162,8 @@ func (pm *PolicyManagerV1) Tags(node *types.Node) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
tags, _ := pm.pol.TagsOfNode(node)
|
||||
tags, invalid := pm.pol.TagsOfNode(pm.users, node)
|
||||
log.Debug().Strs("authorised_tags", tags).Strs("unauthorised_tags", invalid).Uint64("node.id", node.ID.Uint64()).Msg("tags provided by policy")
|
||||
return tags
|
||||
}
|
||||
|
||||
|
@@ -387,7 +387,7 @@ func (m *mapSession) serveLongPoll() {
|
||||
|
||||
func (m *mapSession) pollFailoverRoutes(where string, node *types.Node) {
|
||||
update, err := db.Write(m.h.db.DB, func(tx *gorm.DB) (*types.StateUpdate, error) {
|
||||
return db.FailoverNodeRoutesIfNeccessary(tx, m.h.nodeNotifier.LikelyConnectedMap(), node)
|
||||
return db.FailoverNodeRoutesIfNecessary(tx, m.h.nodeNotifier.LikelyConnectedMap(), node)
|
||||
})
|
||||
if err != nil {
|
||||
m.errf(err, fmt.Sprintf("failed to ensure failover routes, %s", where))
|
||||
@@ -453,7 +453,7 @@ func (m *mapSession) handleEndpointUpdate() {
|
||||
// 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
|
||||
// TODO(kradalby): evaluate if we need better comparing of hostinfo
|
||||
// before we take the changes.
|
||||
if m.req.Hostinfo.NetInfo == nil && m.node.Hostinfo != nil {
|
||||
m.req.Hostinfo.NetInfo = m.node.Hostinfo.NetInfo
|
||||
|
@@ -27,50 +27,27 @@ func Apple(url string) *elem.Element {
|
||||
elem.Text("App store"),
|
||||
),
|
||||
),
|
||||
elem.Li(nil,
|
||||
elem.Text("Open Tailscale and make sure you are "),
|
||||
elem.I(nil, elem.Text("not ")),
|
||||
elem.Text("logged in to any account"),
|
||||
),
|
||||
elem.Li(nil,
|
||||
elem.Text("Open Settings on the iOS device"),
|
||||
elem.Li(
|
||||
nil,
|
||||
elem.Text("Open the Tailscale app"),
|
||||
),
|
||||
elem.Li(
|
||||
nil,
|
||||
elem.Text(
|
||||
`Scroll down to the "third party apps" section, under "Game Center" or "TV Provider"`,
|
||||
elem.Text(`Click the account icon in the top-right corner and select "Log in…".`),
|
||||
),
|
||||
),
|
||||
elem.Li(nil,
|
||||
elem.Text("Find Tailscale and select it"),
|
||||
elem.Ul(nil,
|
||||
elem.Li(
|
||||
nil,
|
||||
elem.Text(
|
||||
`If the iOS device was previously logged into Tailscale, switch the "Reset Keychain" toggle to "on"`,
|
||||
),
|
||||
),
|
||||
),
|
||||
elem.Text(`Tap the top-right options menu button and select "Use custom coordination server".`),
|
||||
),
|
||||
elem.Li(
|
||||
nil,
|
||||
elem.Text(
|
||||
fmt.Sprintf(
|
||||
`Enter "%s" under "Alternate Coordination Server URL"`,
|
||||
`Enter your instance URL: "%s"`,
|
||||
url,
|
||||
),
|
||||
),
|
||||
),
|
||||
elem.Li(
|
||||
nil,
|
||||
elem.Text(
|
||||
"Restart the app by closing it from the iOS app switcher, open the app and select the regular sign in option ",
|
||||
),
|
||||
elem.I(nil, elem.Text("(non-SSO)")),
|
||||
elem.Text(
|
||||
". It should open up to the headscale authentication page.",
|
||||
),
|
||||
),
|
||||
elem.Li(
|
||||
nil,
|
||||
elem.Text(
|
||||
@@ -93,7 +70,7 @@ func Apple(url string) *elem.Element {
|
||||
elem.Li(
|
||||
nil,
|
||||
elem.Text(
|
||||
"ALT + Click the Tailscale icon in the menu and hover over the Debug menu",
|
||||
"Option + Click the Tailscale icon in the menu and hover over the Debug menu",
|
||||
),
|
||||
),
|
||||
elem.Li(nil,
|
||||
|
@@ -26,11 +26,14 @@ import (
|
||||
const (
|
||||
defaultOIDCExpiryTime = 180 * 24 * time.Hour // 180 Days
|
||||
maxDuration time.Duration = 1<<63 - 1
|
||||
PKCEMethodPlain string = "plain"
|
||||
PKCEMethodS256 string = "S256"
|
||||
)
|
||||
|
||||
var (
|
||||
errOidcMutuallyExclusive = errors.New("oidc_client_secret and oidc_client_secret_path are mutually exclusive")
|
||||
errServerURLSuffix = errors.New("server_url cannot be part of base_domain in a way that could make the DERP and headscale server unreachable")
|
||||
errInvalidPKCEMethod = errors.New("pkce.method must be either 'plain' or 'S256'")
|
||||
)
|
||||
|
||||
type IPAllocationStrategy string
|
||||
@@ -162,6 +165,11 @@ type LetsEncryptConfig struct {
|
||||
ChallengeType string
|
||||
}
|
||||
|
||||
type PKCEConfig struct {
|
||||
Enabled bool
|
||||
Method string
|
||||
}
|
||||
|
||||
type OIDCConfig struct {
|
||||
OnlyStartIfOIDCIsAvailable bool
|
||||
Issuer string
|
||||
@@ -176,6 +184,7 @@ type OIDCConfig struct {
|
||||
Expiry time.Duration
|
||||
UseExpiryFromToken bool
|
||||
MapLegacyUsers bool
|
||||
PKCE PKCEConfig
|
||||
}
|
||||
|
||||
type DERPConfig struct {
|
||||
@@ -211,6 +220,10 @@ type PolicyConfig struct {
|
||||
Mode PolicyMode
|
||||
}
|
||||
|
||||
func (p *PolicyConfig) IsEmpty() bool {
|
||||
return p.Mode == PolicyModeFile && p.Path == ""
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Format string
|
||||
Level zerolog.Level
|
||||
@@ -222,6 +235,24 @@ type Tuning struct {
|
||||
NodeMapSessionBufferedChanSize int
|
||||
}
|
||||
|
||||
func validatePKCEMethod(method string) error {
|
||||
if method != PKCEMethodPlain && method != PKCEMethodS256 {
|
||||
return errInvalidPKCEMethod
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Domain returns the hostname/domain part of the ServerURL.
|
||||
// If the ServerURL is not a valid URL, it returns the BaseDomain.
|
||||
func (c *Config) Domain() string {
|
||||
u, err := url.Parse(c.ServerURL)
|
||||
if err != nil {
|
||||
return c.BaseDomain
|
||||
}
|
||||
|
||||
return u.Hostname()
|
||||
}
|
||||
|
||||
// LoadConfig prepares and loads the Headscale configuration into Viper.
|
||||
// This means it sets the default values, reads the configuration file and
|
||||
// environment variables, and handles deprecated configuration options.
|
||||
@@ -289,6 +320,8 @@ func LoadConfig(path string, isFile bool) error {
|
||||
viper.SetDefault("oidc.expiry", "180d")
|
||||
viper.SetDefault("oidc.use_expiry_from_token", false)
|
||||
viper.SetDefault("oidc.map_legacy_users", true)
|
||||
viper.SetDefault("oidc.pkce.enabled", false)
|
||||
viper.SetDefault("oidc.pkce.method", "S256")
|
||||
|
||||
viper.SetDefault("logtail.enabled", false)
|
||||
viper.SetDefault("randomize_client_port", false)
|
||||
@@ -336,6 +369,12 @@ func validateServerConfig() error {
|
||||
// after #2170 is cleaned up
|
||||
// depr.fatal("oidc.strip_email_domain")
|
||||
|
||||
if viper.GetBool("oidc.enabled") {
|
||||
if err := validatePKCEMethod(viper.GetString("oidc.pkce.method")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
depr.Log()
|
||||
|
||||
for _, removed := range []string{
|
||||
@@ -589,7 +628,7 @@ func dns() (DNSConfig, error) {
|
||||
// UnmarshalKey is compatible with Environment Variables.
|
||||
// err := viper.UnmarshalKey("dns", &dns)
|
||||
// if err != nil {
|
||||
// return DNSConfig{}, fmt.Errorf("unmarshaling dns config: %w", err)
|
||||
// return DNSConfig{}, fmt.Errorf("unmarshalling dns config: %w", err)
|
||||
// }
|
||||
|
||||
dns.MagicDNS = viper.GetBool("dns.magic_dns")
|
||||
@@ -604,7 +643,7 @@ func dns() (DNSConfig, error) {
|
||||
|
||||
err := viper.UnmarshalKey("dns.extra_records", &extraRecords)
|
||||
if err != nil {
|
||||
return DNSConfig{}, fmt.Errorf("unmarshaling dns extra records: %w", err)
|
||||
return DNSConfig{}, fmt.Errorf("unmarshalling dns extra records: %w", err)
|
||||
}
|
||||
dns.ExtraRecords = extraRecords
|
||||
}
|
||||
@@ -924,6 +963,10 @@ func LoadServerConfig() (*Config, error) {
|
||||
// after #2170 is cleaned up
|
||||
StripEmaildomain: viper.GetBool("oidc.strip_email_domain"),
|
||||
MapLegacyUsers: viper.GetBool("oidc.map_legacy_users"),
|
||||
PKCE: PKCEConfig{
|
||||
Enabled: viper.GetBool("oidc.pkce.enabled"),
|
||||
Method: viper.GetString("oidc.pkce.method"),
|
||||
},
|
||||
},
|
||||
|
||||
LogTail: logTailConfig,
|
||||
|
@@ -3,11 +3,14 @@ package types
|
||||
import (
|
||||
"cmp"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"strconv"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"gorm.io/gorm"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -119,6 +122,37 @@ func (u *User) Proto() *v1.User {
|
||||
}
|
||||
}
|
||||
|
||||
// JumpCloud returns a JSON where email_verified is returned as a
|
||||
// string "true" or "false" instead of a boolean.
|
||||
// This maps bool to a specific type with a custom unmarshaler to
|
||||
// ensure we can decode it from a string.
|
||||
// https://github.com/juanfont/headscale/issues/2293
|
||||
type FlexibleBoolean bool
|
||||
|
||||
func (bit *FlexibleBoolean) UnmarshalJSON(data []byte) error {
|
||||
var val interface{}
|
||||
err := json.Unmarshal(data, &val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not unmarshal data: %w", err)
|
||||
}
|
||||
|
||||
switch v := val.(type) {
|
||||
case bool:
|
||||
*bit = FlexibleBoolean(v)
|
||||
case string:
|
||||
pv, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as boolean: %w", v, err)
|
||||
}
|
||||
*bit = FlexibleBoolean(pv)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("could not parse %v as boolean", v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type OIDCClaims struct {
|
||||
// Sub is the user's unique identifier at the provider.
|
||||
Sub string `json:"sub"`
|
||||
@@ -128,7 +162,7 @@ type OIDCClaims struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
EmailVerified bool `json:"email_verified,omitempty"`
|
||||
EmailVerified FlexibleBoolean `json:"email_verified,omitempty"`
|
||||
ProfilePictureURL string `json:"picture,omitempty"`
|
||||
Username string `json:"preferred_username,omitempty"`
|
||||
}
|
||||
@@ -140,9 +174,11 @@ func (c *OIDCClaims) Identifier() string {
|
||||
// FromClaim overrides a User from OIDC claims.
|
||||
// All fields will be updated, except for the ID.
|
||||
func (u *User) FromClaim(claims *OIDCClaims) {
|
||||
err := util.CheckForFQDNRules(claims.Username)
|
||||
err := util.ValidateUsername(claims.Username)
|
||||
if err == nil {
|
||||
u.Name = claims.Username
|
||||
} else {
|
||||
log.Debug().Err(err).Msgf("Username %s is not valid", claims.Username)
|
||||
}
|
||||
|
||||
if claims.EmailVerified {
|
||||
|
75
hscontrol/types/users_test.go
Normal file
75
hscontrol/types/users_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestUnmarshallOIDCClaims(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
jsonstr string
|
||||
want OIDCClaims
|
||||
}{
|
||||
{
|
||||
name: "normal-bool",
|
||||
jsonstr: `
|
||||
{
|
||||
"sub": "test",
|
||||
"email": "test@test.no",
|
||||
"email_verified": true
|
||||
}
|
||||
`,
|
||||
want: OIDCClaims{
|
||||
Sub: "test",
|
||||
Email: "test@test.no",
|
||||
EmailVerified: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string-bool-true",
|
||||
jsonstr: `
|
||||
{
|
||||
"sub": "test2",
|
||||
"email": "test2@test.no",
|
||||
"email_verified": "true"
|
||||
}
|
||||
`,
|
||||
want: OIDCClaims{
|
||||
Sub: "test2",
|
||||
Email: "test2@test.no",
|
||||
EmailVerified: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string-bool-false",
|
||||
jsonstr: `
|
||||
{
|
||||
"sub": "test3",
|
||||
"email": "test3@test.no",
|
||||
"email_verified": "false"
|
||||
}
|
||||
`,
|
||||
want: OIDCClaims{
|
||||
Sub: "test3",
|
||||
Email: "test3@test.no",
|
||||
EmailVerified: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var got OIDCClaims
|
||||
if err := json.Unmarshal([]byte(tt.jsonstr), &got); err != nil {
|
||||
t.Errorf("UnmarshallOIDCClaims() error = %v", err)
|
||||
return
|
||||
}
|
||||
if diff := cmp.Diff(got, tt.want); diff != "" {
|
||||
t.Errorf("UnmarshallOIDCClaims() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -6,6 +6,7 @@ import (
|
||||
"net/netip"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"go4.org/netipx"
|
||||
"tailscale.com/util/dnsname"
|
||||
@@ -20,10 +21,40 @@ const (
|
||||
LabelHostnameLength = 63
|
||||
)
|
||||
|
||||
var invalidDNSRegex = regexp.MustCompile("[^a-z0-9-.]+")
|
||||
var invalidCharsInUserRegex = regexp.MustCompile("[^a-z0-9-.]+")
|
||||
|
||||
var ErrInvalidUserName = errors.New("invalid user name")
|
||||
|
||||
func ValidateUsername(username string) error {
|
||||
// Ensure the username meets the minimum length requirement
|
||||
if len(username) < 2 {
|
||||
return errors.New("username must be at least 2 characters long")
|
||||
}
|
||||
|
||||
// Ensure the username does not start with a number
|
||||
if unicode.IsDigit(rune(username[0])) {
|
||||
return errors.New("username cannot start with a number")
|
||||
}
|
||||
|
||||
atCount := 0
|
||||
for _, char := range username {
|
||||
switch {
|
||||
case unicode.IsLetter(char), unicode.IsDigit(char), char == '-':
|
||||
// Valid characters
|
||||
case char == '@':
|
||||
atCount++
|
||||
if atCount > 1 {
|
||||
return errors.New("username cannot contain more than one '@'")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("username contains invalid character: '%c'", char)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckForFQDNRules(name string) error {
|
||||
if len(name) > LabelHostnameLength {
|
||||
return fmt.Errorf(
|
||||
@@ -39,7 +70,7 @@ func CheckForFQDNRules(name string) error {
|
||||
ErrInvalidUserName,
|
||||
)
|
||||
}
|
||||
if invalidCharsInUserRegex.MatchString(name) {
|
||||
if invalidDNSRegex.MatchString(name) {
|
||||
return fmt.Errorf(
|
||||
"DNS segment should only be composed of lowercase ASCII letters numbers, hyphen and dots. %v doesn't comply with theses rules: %w",
|
||||
name,
|
||||
@@ -52,7 +83,7 @@ func CheckForFQDNRules(name string) error {
|
||||
|
||||
func ConvertWithFQDNRules(name string) string {
|
||||
name = strings.ToLower(name)
|
||||
name = invalidCharsInUserRegex.ReplaceAllString(name, "")
|
||||
name = invalidDNSRegex.ReplaceAllString(name, "")
|
||||
|
||||
return name
|
||||
}
|
||||
@@ -197,7 +228,7 @@ func NormalizeToFQDNRules(name string, stripEmailDomain bool) (string, error) {
|
||||
} else {
|
||||
name = strings.ReplaceAll(name, "@", ".")
|
||||
}
|
||||
name = invalidCharsInUserRegex.ReplaceAllString(name, "-")
|
||||
name = invalidDNSRegex.ReplaceAllString(name, "-")
|
||||
|
||||
for _, elt := range strings.Split(name, ".") {
|
||||
if len(elt) > LabelHostnameLength {
|
||||
|
@@ -119,8 +119,8 @@ func TestACLHostsInNetMapTable(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, want: map[string]int{
|
||||
"user1": 3, // ns1 + ns2
|
||||
"user2": 3, // ns2 + ns1
|
||||
"user1@test.no": 3, // ns1 + ns2
|
||||
"user2@test.no": 3, // ns2 + ns1
|
||||
},
|
||||
},
|
||||
// Test that when we have two users, which cannot see
|
||||
@@ -145,8 +145,8 @@ func TestACLHostsInNetMapTable(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, want: map[string]int{
|
||||
"user1": 1,
|
||||
"user2": 1,
|
||||
"user1@test.no": 1,
|
||||
"user2@test.no": 1,
|
||||
},
|
||||
},
|
||||
// Test that when we have two users, with ACLs and they
|
||||
@@ -181,8 +181,8 @@ func TestACLHostsInNetMapTable(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, want: map[string]int{
|
||||
"user1": 3,
|
||||
"user2": 3,
|
||||
"user1@test.no": 3,
|
||||
"user2@test.no": 3,
|
||||
},
|
||||
},
|
||||
// Test that when we have two users, that are isolated,
|
||||
@@ -213,8 +213,8 @@ func TestACLHostsInNetMapTable(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, want: map[string]int{
|
||||
"user1": 3, // ns1 + ns2
|
||||
"user2": 3, // ns1 + ns2 (return path)
|
||||
"user1@test.no": 3, // ns1 + ns2
|
||||
"user2@test.no": 3, // ns1 + ns2 (return path)
|
||||
},
|
||||
},
|
||||
"very-large-destination-prefix-1372": {
|
||||
@@ -241,8 +241,8 @@ func TestACLHostsInNetMapTable(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, want: map[string]int{
|
||||
"user1": 3, // ns1 + ns2
|
||||
"user2": 3, // ns1 + ns2 (return path)
|
||||
"user1@test.no": 3, // ns1 + ns2
|
||||
"user2@test.no": 3, // ns1 + ns2 (return path)
|
||||
},
|
||||
},
|
||||
"ipv6-acls-1470": {
|
||||
@@ -259,8 +259,8 @@ func TestACLHostsInNetMapTable(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, want: map[string]int{
|
||||
"user1": 3, // ns1 + ns2
|
||||
"user2": 3, // ns2 + ns1
|
||||
"user1@test.no": 3, // ns1 + ns2
|
||||
"user2@test.no": 3, // ns2 + ns1
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -282,7 +282,7 @@ func TestACLHostsInNetMapTable(t *testing.T) {
|
||||
allClients, err := scenario.ListTailscaleClients()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = scenario.WaitForTailscaleSyncWithPeerCount(testCase.want["user1"])
|
||||
err = scenario.WaitForTailscaleSyncWithPeerCount(testCase.want["user1@test.no"])
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, client := range allClients {
|
||||
|
@@ -10,6 +10,8 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -132,6 +134,7 @@ func TestOIDCAuthenticationPingAll(t *testing.T) {
|
||||
{
|
||||
Id: 1,
|
||||
Name: "user1",
|
||||
Email: "user1@test.no",
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
@@ -143,6 +146,7 @@ func TestOIDCAuthenticationPingAll(t *testing.T) {
|
||||
{
|
||||
Id: 3,
|
||||
Name: "user2",
|
||||
Email: "user2@test.no",
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
@@ -262,6 +266,7 @@ func TestOIDC024UserCreation(t *testing.T) {
|
||||
{
|
||||
Id: 1,
|
||||
Name: "user1",
|
||||
Email: "user1@test.no",
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
@@ -273,6 +278,7 @@ func TestOIDC024UserCreation(t *testing.T) {
|
||||
{
|
||||
Id: 3,
|
||||
Name: "user2",
|
||||
Email: "user2@test.no",
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
@@ -297,6 +303,7 @@ func TestOIDC024UserCreation(t *testing.T) {
|
||||
{
|
||||
Id: 1,
|
||||
Name: "user1",
|
||||
Email: "user1@test.no",
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
@@ -307,6 +314,7 @@ func TestOIDC024UserCreation(t *testing.T) {
|
||||
{
|
||||
Id: 3,
|
||||
Name: "user2",
|
||||
Email: "user2@test.no",
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
@@ -359,6 +367,7 @@ func TestOIDC024UserCreation(t *testing.T) {
|
||||
{
|
||||
Id: 1,
|
||||
Name: "user1",
|
||||
Email: "user1@test.no",
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
@@ -369,6 +378,7 @@ func TestOIDC024UserCreation(t *testing.T) {
|
||||
{
|
||||
Id: 3,
|
||||
Name: "user2",
|
||||
Email: "user2@test.no",
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
@@ -423,6 +433,7 @@ func TestOIDC024UserCreation(t *testing.T) {
|
||||
{
|
||||
Id: 1,
|
||||
Name: "user1.headscale.net",
|
||||
Email: "user1.headscale.net@test.no",
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
@@ -433,6 +444,7 @@ func TestOIDC024UserCreation(t *testing.T) {
|
||||
{
|
||||
Id: 3,
|
||||
Name: "user2.headscale.net",
|
||||
Email: "user2.headscale.net@test.no",
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
@@ -524,6 +536,86 @@ func TestOIDC024UserCreation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOIDCAuthenticationWithPKCE(t *testing.T) {
|
||||
IntegrationSkip(t)
|
||||
t.Parallel()
|
||||
|
||||
baseScenario, err := NewScenario(dockertestMaxWait())
|
||||
assertNoErr(t, err)
|
||||
|
||||
scenario := AuthOIDCScenario{
|
||||
Scenario: baseScenario,
|
||||
}
|
||||
defer scenario.ShutdownAssertNoPanics(t)
|
||||
|
||||
// Single user with one node for testing PKCE flow
|
||||
spec := map[string]int{
|
||||
"user1": 1,
|
||||
}
|
||||
|
||||
mockusers := []mockoidc.MockUser{
|
||||
oidcMockUser("user1", true),
|
||||
}
|
||||
|
||||
oidcConfig, err := scenario.runMockOIDC(defaultAccessTTL, mockusers)
|
||||
assertNoErrf(t, "failed to run mock OIDC server: %s", err)
|
||||
defer scenario.mockOIDC.Close()
|
||||
|
||||
oidcMap := map[string]string{
|
||||
"HEADSCALE_OIDC_ISSUER": oidcConfig.Issuer,
|
||||
"HEADSCALE_OIDC_CLIENT_ID": oidcConfig.ClientID,
|
||||
"HEADSCALE_OIDC_CLIENT_SECRET_PATH": "${CREDENTIALS_DIRECTORY_TEST}/hs_client_oidc_secret",
|
||||
"CREDENTIALS_DIRECTORY_TEST": "/tmp",
|
||||
"HEADSCALE_OIDC_PKCE_ENABLED": "1", // Enable PKCE
|
||||
"HEADSCALE_OIDC_MAP_LEGACY_USERS": "0",
|
||||
"HEADSCALE_OIDC_STRIP_EMAIL_DOMAIN": "0",
|
||||
}
|
||||
|
||||
err = scenario.CreateHeadscaleEnv(
|
||||
spec,
|
||||
hsic.WithTestName("oidcauthpkce"),
|
||||
hsic.WithConfigEnv(oidcMap),
|
||||
hsic.WithTLS(),
|
||||
hsic.WithHostnameAsServerURL(),
|
||||
hsic.WithFileInContainer("/tmp/hs_client_oidc_secret", []byte(oidcConfig.ClientSecret)),
|
||||
)
|
||||
assertNoErrHeadscaleEnv(t, err)
|
||||
|
||||
// Get all clients and verify they can connect
|
||||
allClients, err := scenario.ListTailscaleClients()
|
||||
assertNoErrListClients(t, err)
|
||||
|
||||
allIps, err := scenario.ListTailscaleClientsIPs()
|
||||
assertNoErrListClientIPs(t, err)
|
||||
|
||||
err = scenario.WaitForTailscaleSync()
|
||||
assertNoErrSync(t, err)
|
||||
|
||||
// Verify PKCE was used in authentication
|
||||
headscale, err := scenario.Headscale()
|
||||
assertNoErr(t, err)
|
||||
|
||||
var listUsers []v1.User
|
||||
err = executeAndUnmarshal(headscale,
|
||||
[]string{
|
||||
"headscale",
|
||||
"users",
|
||||
"list",
|
||||
"--output",
|
||||
"json",
|
||||
},
|
||||
&listUsers,
|
||||
)
|
||||
assertNoErr(t, err)
|
||||
|
||||
allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string {
|
||||
return x.String()
|
||||
})
|
||||
|
||||
success := pingAllHelper(t, allClients, allAddrs)
|
||||
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))
|
||||
}
|
||||
|
||||
func (s *AuthOIDCScenario) CreateHeadscaleEnv(
|
||||
users map[string]int,
|
||||
opts ...hsic.Option,
|
||||
@@ -657,6 +749,24 @@ func (s *AuthOIDCScenario) runMockOIDC(accessTTL time.Duration, users []mockoidc
|
||||
}, nil
|
||||
}
|
||||
|
||||
type LoggingRoundTripper struct{}
|
||||
|
||||
func (t LoggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
noTls := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // nolint
|
||||
}
|
||||
resp, err := noTls.RoundTrip(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("---")
|
||||
log.Printf("method: %s | url: %s", resp.Request.Method, resp.Request.URL.String())
|
||||
log.Printf("status: %d | cookies: %+v", resp.StatusCode, resp.Cookies())
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *AuthOIDCScenario) runTailscaleUp(
|
||||
userStr, loginServer string,
|
||||
) error {
|
||||
@@ -668,35 +778,39 @@ func (s *AuthOIDCScenario) runTailscaleUp(
|
||||
log.Printf("running tailscale up for user %s", userStr)
|
||||
if user, ok := s.users[userStr]; ok {
|
||||
for _, client := range user.Clients {
|
||||
c := client
|
||||
tsc := client
|
||||
user.joinWaitGroup.Go(func() error {
|
||||
loginURL, err := c.LoginWithURL(loginServer)
|
||||
loginURL, err := tsc.LoginWithURL(loginServer)
|
||||
if err != nil {
|
||||
log.Printf("%s failed to run tailscale up: %s", c.Hostname(), err)
|
||||
log.Printf("%s failed to run tailscale up: %s", tsc.Hostname(), err)
|
||||
}
|
||||
|
||||
loginURL.Host = fmt.Sprintf("%s:8080", headscale.GetIP())
|
||||
loginURL.Host = fmt.Sprintf("%s:8080", headscale.GetHostname())
|
||||
loginURL.Scheme = "http"
|
||||
|
||||
if len(headscale.GetCert()) > 0 {
|
||||
loginURL.Scheme = "https"
|
||||
}
|
||||
|
||||
insecureTransport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // nolint
|
||||
httptest.NewRecorder()
|
||||
hc := &http.Client{
|
||||
Transport: LoggingRoundTripper{},
|
||||
}
|
||||
hc.Jar, err = cookiejar.New(nil)
|
||||
if err != nil {
|
||||
log.Printf("failed to create cookie jar: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("%s login url: %s\n", c.Hostname(), loginURL.String())
|
||||
log.Printf("%s login url: %s\n", tsc.Hostname(), loginURL.String())
|
||||
|
||||
log.Printf("%s logging in with url", c.Hostname())
|
||||
httpClient := &http.Client{Transport: insecureTransport}
|
||||
log.Printf("%s logging in with url", tsc.Hostname())
|
||||
ctx := context.Background()
|
||||
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, loginURL.String(), nil)
|
||||
resp, err := httpClient.Do(req)
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
log.Printf(
|
||||
"%s failed to login using url %s: %s",
|
||||
c.Hostname(),
|
||||
tsc.Hostname(),
|
||||
loginURL,
|
||||
err,
|
||||
)
|
||||
@@ -704,8 +818,10 @@ func (s *AuthOIDCScenario) runTailscaleUp(
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("cookies: %+v", hc.Jar.Cookies(loginURL))
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Printf("%s response code of oidc login request was %s", c.Hostname(), resp.Status)
|
||||
log.Printf("%s response code of oidc login request was %s", tsc.Hostname(), resp.Status)
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
log.Printf("body: %s", body)
|
||||
|
||||
@@ -716,12 +832,12 @@ func (s *AuthOIDCScenario) runTailscaleUp(
|
||||
|
||||
_, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("%s failed to read response body: %s", c.Hostname(), err)
|
||||
log.Printf("%s failed to read response body: %s", tsc.Hostname(), err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Finished request for %s to join tailnet", c.Hostname())
|
||||
log.Printf("Finished request for %s to join tailnet", tsc.Hostname())
|
||||
return nil
|
||||
})
|
||||
|
||||
|
@@ -137,6 +137,7 @@ func TestUserCommand(t *testing.T) {
|
||||
{
|
||||
Id: 1,
|
||||
Name: "user1",
|
||||
Email: "user1@test.no",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -163,6 +164,7 @@ func TestUserCommand(t *testing.T) {
|
||||
{
|
||||
Id: 1,
|
||||
Name: "user1",
|
||||
Email: "user1@test.no",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -201,6 +203,7 @@ func TestUserCommand(t *testing.T) {
|
||||
{
|
||||
Id: 2,
|
||||
Name: "newname",
|
||||
Email: "user2@test.no",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -930,7 +933,23 @@ func TestNodeAdvertiseTagCommand(t *testing.T) {
|
||||
wantTag: false,
|
||||
},
|
||||
{
|
||||
name: "with-policy",
|
||||
name: "with-policy-email",
|
||||
policy: &policy.ACLPolicy{
|
||||
ACLs: []policy.ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Sources: []string{"*"},
|
||||
Destinations: []string{"*:*"},
|
||||
},
|
||||
},
|
||||
TagOwners: map[string][]string{
|
||||
"tag:test": {"user1@test.no"},
|
||||
},
|
||||
},
|
||||
wantTag: true,
|
||||
},
|
||||
{
|
||||
name: "with-policy-username",
|
||||
policy: &policy.ACLPolicy{
|
||||
ACLs: []policy.ACL{
|
||||
{
|
||||
@@ -945,13 +964,32 @@ func TestNodeAdvertiseTagCommand(t *testing.T) {
|
||||
},
|
||||
wantTag: true,
|
||||
},
|
||||
{
|
||||
name: "with-policy-groups",
|
||||
policy: &policy.ACLPolicy{
|
||||
Groups: policy.Groups{
|
||||
"group:admins": []string{"user1"},
|
||||
},
|
||||
ACLs: []policy.ACL{
|
||||
{
|
||||
Action: "accept",
|
||||
Sources: []string{"*"},
|
||||
Destinations: []string{"*:*"},
|
||||
},
|
||||
},
|
||||
TagOwners: map[string][]string{
|
||||
"tag:test": {"group:admins"},
|
||||
},
|
||||
},
|
||||
wantTag: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
scenario, err := NewScenario(dockertestMaxWait())
|
||||
assertNoErr(t, err)
|
||||
// defer scenario.ShutdownAssertNoPanics(t)
|
||||
defer scenario.ShutdownAssertNoPanics(t)
|
||||
|
||||
spec := map[string]int{
|
||||
"user1": 1,
|
||||
|
@@ -146,6 +146,27 @@ func TestResolveMagicDNSExtraRecordsPath(t *testing.T) {
|
||||
assertCommandOutputContains(t, client, []string{"dig", "test.myvpn.example.com"}, "6.6.6.6")
|
||||
}
|
||||
|
||||
hs, err := scenario.Headscale()
|
||||
assertNoErr(t, err)
|
||||
|
||||
// Write the file directly into place from the docker API.
|
||||
b0, _ := json.Marshal([]tailcfg.DNSRecord{
|
||||
{
|
||||
Name: "docker.myvpn.example.com",
|
||||
Type: "A",
|
||||
Value: "2.2.2.2",
|
||||
},
|
||||
})
|
||||
|
||||
err = hs.WriteFile(erPath, b0)
|
||||
assertNoErr(t, err)
|
||||
|
||||
for _, client := range allClients {
|
||||
assertCommandOutputContains(t, client, []string{"dig", "docker.myvpn.example.com"}, "2.2.2.2")
|
||||
}
|
||||
|
||||
// Write a new file and move it to the path to ensure the reload
|
||||
// works when a file is moved atomically into place.
|
||||
extraRecords = append(extraRecords, tailcfg.DNSRecord{
|
||||
Name: "otherrecord.myvpn.example.com",
|
||||
Type: "A",
|
||||
@@ -153,12 +174,6 @@ func TestResolveMagicDNSExtraRecordsPath(t *testing.T) {
|
||||
})
|
||||
b2, _ := json.Marshal(extraRecords)
|
||||
|
||||
hs, err := scenario.Headscale()
|
||||
assertNoErr(t, err)
|
||||
|
||||
// Write it to a separate file to ensure Docker's API doesnt
|
||||
// do anything unexpected and rather move it into place to trigger
|
||||
// a reload.
|
||||
err = hs.WriteFile(erPath+"2", b2)
|
||||
assertNoErr(t, err)
|
||||
_, err = hs.Execute([]string{"mv", erPath + "2", erPath})
|
||||
@@ -168,6 +183,61 @@ func TestResolveMagicDNSExtraRecordsPath(t *testing.T) {
|
||||
assertCommandOutputContains(t, client, []string{"dig", "test.myvpn.example.com"}, "6.6.6.6")
|
||||
assertCommandOutputContains(t, client, []string{"dig", "otherrecord.myvpn.example.com"}, "7.7.7.7")
|
||||
}
|
||||
|
||||
// Write a new file and copy it to the path to ensure the reload
|
||||
// works when a file is copied into place.
|
||||
b3, _ := json.Marshal([]tailcfg.DNSRecord{
|
||||
{
|
||||
Name: "copy.myvpn.example.com",
|
||||
Type: "A",
|
||||
Value: "8.8.8.8",
|
||||
},
|
||||
})
|
||||
|
||||
err = hs.WriteFile(erPath+"3", b3)
|
||||
assertNoErr(t, err)
|
||||
_, err = hs.Execute([]string{"cp", erPath + "3", erPath})
|
||||
assertNoErr(t, err)
|
||||
|
||||
for _, client := range allClients {
|
||||
assertCommandOutputContains(t, client, []string{"dig", "copy.myvpn.example.com"}, "8.8.8.8")
|
||||
}
|
||||
|
||||
// Write in place to ensure pipe like behaviour works
|
||||
b4, _ := json.Marshal([]tailcfg.DNSRecord{
|
||||
{
|
||||
Name: "docker.myvpn.example.com",
|
||||
Type: "A",
|
||||
Value: "9.9.9.9",
|
||||
},
|
||||
})
|
||||
command := []string{"echo", fmt.Sprintf("'%s'", string(b4)), ">", erPath}
|
||||
_, err = hs.Execute([]string{"bash", "-c", strings.Join(command, " ")})
|
||||
assertNoErr(t, err)
|
||||
|
||||
for _, client := range allClients {
|
||||
assertCommandOutputContains(t, client, []string{"dig", "docker.myvpn.example.com"}, "9.9.9.9")
|
||||
}
|
||||
|
||||
// Delete the file and create a new one to ensure it is picked up again.
|
||||
_, err = hs.Execute([]string{"rm", erPath})
|
||||
assertNoErr(t, err)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// The same paths should still be available as it is not cleared on delete.
|
||||
for _, client := range allClients {
|
||||
assertCommandOutputContains(t, client, []string{"dig", "docker.myvpn.example.com"}, "9.9.9.9")
|
||||
}
|
||||
|
||||
// Write a new file, the backoff mechanism should make the filewatcher pick it up
|
||||
// again.
|
||||
err = hs.WriteFile(erPath, b3)
|
||||
assertNoErr(t, err)
|
||||
|
||||
for _, client := range allClients {
|
||||
assertCommandOutputContains(t, client, []string{"dig", "copy.myvpn.example.com"}, "8.8.8.8")
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateResolvConf validates that the resolv.conf file
|
||||
|
@@ -25,7 +25,6 @@ type ExecuteCommandOption func(*ExecuteCommandConfig) error
|
||||
func ExecuteCommandTimeout(timeout time.Duration) ExecuteCommandOption {
|
||||
return ExecuteCommandOption(func(conf *ExecuteCommandConfig) error {
|
||||
conf.timeout = timeout
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -67,6 +66,7 @@ func ExecuteCommand(
|
||||
StdErr: &stderr,
|
||||
},
|
||||
)
|
||||
|
||||
resultChan <- result{exitCode, err}
|
||||
}()
|
||||
|
||||
@@ -88,7 +88,6 @@ func ExecuteCommand(
|
||||
|
||||
return stdout.String(), stderr.String(), nil
|
||||
case <-time.After(execConfig.timeout):
|
||||
|
||||
return stdout.String(), stderr.String(), fmt.Errorf("command failed, stderr: %s: %w", stderr.String(), ErrDockertestCommandTimeout)
|
||||
}
|
||||
}
|
||||
|
@@ -702,7 +702,7 @@ func (t *HeadscaleInContainer) WaitForRunning() error {
|
||||
func (t *HeadscaleInContainer) CreateUser(
|
||||
user string,
|
||||
) error {
|
||||
command := []string{"headscale", "users", "create", user}
|
||||
command := []string{"headscale", "users", "create", user, fmt.Sprintf("--email=%s@test.no", user)}
|
||||
|
||||
_, _, err := dockertestutil.ExecuteCommand(
|
||||
t.container,
|
||||
|
@@ -372,18 +372,22 @@ func (s *Scenario) CreateTailscaleNodesInUser(
|
||||
cert := headscale.GetCert()
|
||||
hostname := headscale.GetHostname()
|
||||
|
||||
s.mu.Lock()
|
||||
opts = append(opts,
|
||||
tsic.WithCACert(cert),
|
||||
tsic.WithHeadscaleName(hostname),
|
||||
)
|
||||
s.mu.Unlock()
|
||||
|
||||
user.createWaitGroup.Go(func() error {
|
||||
s.mu.Lock()
|
||||
tsClient, err := tsic.New(
|
||||
s.pool,
|
||||
version,
|
||||
s.network,
|
||||
opts...,
|
||||
)
|
||||
s.mu.Unlock()
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to create tailscale (%s) node: %w",
|
||||
|
@@ -69,9 +69,6 @@ func sshScenario(t *testing.T, policy *policy.ACLPolicy, clientsPerUser int) *Sc
|
||||
},
|
||||
hsic.WithACLPolicy(policy),
|
||||
hsic.WithTestName("ssh"),
|
||||
hsic.WithConfigEnv(map[string]string{
|
||||
"HEADSCALE_EXPERIMENTAL_FEATURE_SSH": "1",
|
||||
}),
|
||||
)
|
||||
assertNoErr(t, err)
|
||||
|
||||
|
@@ -106,7 +106,7 @@ extra:
|
||||
- icon: fontawesome/brands/discord
|
||||
link: https://discord.gg/c84AZQhmpx
|
||||
headscale:
|
||||
version: 0.23.0
|
||||
version: 0.24.0
|
||||
|
||||
# Extensions
|
||||
markdown_extensions:
|
||||
@@ -168,7 +168,6 @@ nav:
|
||||
- Official releases: setup/install/official.md
|
||||
- Community packages: setup/install/community.md
|
||||
- Container: setup/install/container.md
|
||||
- Cloud: setup/install/cloud.md
|
||||
- Build from source: setup/install/source.md
|
||||
- Upgrade: setup/upgrade.md
|
||||
- Usage:
|
||||
|
@@ -15,7 +15,12 @@ message User {
|
||||
string profile_pic_url = 8;
|
||||
}
|
||||
|
||||
message CreateUserRequest { string name = 1; }
|
||||
message CreateUserRequest {
|
||||
string name = 1;
|
||||
string display_name = 2;
|
||||
string email = 3;
|
||||
string picture_url = 4;
|
||||
}
|
||||
|
||||
message CreateUserResponse { User user = 1; }
|
||||
|
||||
|
Reference in New Issue
Block a user