mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-20 03:47:27 +00:00
Compare commits
1 Commits
v0.23.0-al
...
juanfont/b
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7bdf364e51 |
65
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
65
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
name: "Bug report"
|
||||||
|
about: "Create a bug report to help us improve"
|
||||||
|
title: ""
|
||||||
|
labels: ["bug"]
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Before posting a bug report, discuss the behaviour you are expecting with the Discord community
|
||||||
|
to make sure that it is truly a bug.
|
||||||
|
The issue tracker is not the place to ask for support or how to set up Headscale.
|
||||||
|
|
||||||
|
Bug reports without the sufficient information will be closed.
|
||||||
|
|
||||||
|
Headscale is a multinational community across the globe. Our language is English.
|
||||||
|
All bug reports needs to be in English.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Bug description
|
||||||
|
|
||||||
|
<!-- A clear and concise description of what the bug is. Describe the expected bahavior
|
||||||
|
and how it is currently different. If you are unsure if it is a bug, consider discussing
|
||||||
|
it on our Discord server first. -->
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
<!-- Please add relevant information about your system. For example:
|
||||||
|
- Version of headscale used
|
||||||
|
- Version of tailscale client
|
||||||
|
- OS (e.g. Linux, Mac, Cygwin, WSL, etc.) and version
|
||||||
|
- Kernel version
|
||||||
|
- The relevant config parameters you used
|
||||||
|
- Log output
|
||||||
|
-->
|
||||||
|
|
||||||
|
- OS:
|
||||||
|
- Headscale version:
|
||||||
|
- Tailscale version:
|
||||||
|
|
||||||
|
<!--
|
||||||
|
We do not support running Headscale in a container nor behind a (reverse) proxy.
|
||||||
|
If either of these are true for your environment, ask the community in Discord
|
||||||
|
instead of filing a bug report.
|
||||||
|
-->
|
||||||
|
|
||||||
|
- [ ] Headscale is behind a (reverse) proxy
|
||||||
|
- [ ] Headscale runs in a container
|
||||||
|
|
||||||
|
## To Reproduce
|
||||||
|
|
||||||
|
<!-- Steps to reproduce the behavior. -->
|
||||||
|
|
||||||
|
## Logs and attachments
|
||||||
|
|
||||||
|
<!-- Please attach files with:
|
||||||
|
- Client netmap dump (see below)
|
||||||
|
- ACL configuration
|
||||||
|
- Headscale configuration
|
||||||
|
|
||||||
|
Dump the netmap of tailscale clients:
|
||||||
|
`tailscale debug netmap > DESCRIPTIVE_NAME.json`
|
||||||
|
|
||||||
|
Please provide information describing the netmap, which client, which headscale version etc.
|
||||||
|
-->
|
83
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
83
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -1,83 +0,0 @@
|
|||||||
name: 🐞 Bug
|
|
||||||
description: File a bug/issue
|
|
||||||
title: "[Bug] <title>"
|
|
||||||
labels: ["bug", "needs triage"]
|
|
||||||
body:
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: Is this a support request?
|
|
||||||
description: This issue tracker is for bugs and feature requests only. If you need help, please use ask in our Discord community
|
|
||||||
options:
|
|
||||||
- label: This is not a support request
|
|
||||||
required: true
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: Is there an existing issue for this?
|
|
||||||
description: Please search to see if an issue already exists for the bug you encountered.
|
|
||||||
options:
|
|
||||||
- label: I have searched the existing issues
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Current Behavior
|
|
||||||
description: A concise description of what you're experiencing.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Expected Behavior
|
|
||||||
description: A concise description of what you expected to happen.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Steps To Reproduce
|
|
||||||
description: Steps to reproduce the behavior.
|
|
||||||
placeholder: |
|
|
||||||
1. In this environment...
|
|
||||||
1. With this config...
|
|
||||||
1. Run '...'
|
|
||||||
1. See error...
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Environment
|
|
||||||
description: |
|
|
||||||
examples:
|
|
||||||
- **OS**: Ubuntu 20.04
|
|
||||||
- **Headscale version**: 0.22.3
|
|
||||||
- **Tailscale version**: 1.64.0
|
|
||||||
value: |
|
|
||||||
- OS:
|
|
||||||
- Headscale version:
|
|
||||||
- Tailscale version:
|
|
||||||
render: markdown
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: Runtime environment
|
|
||||||
options:
|
|
||||||
- label: Headscale is behind a (reverse) proxy
|
|
||||||
required: false
|
|
||||||
- label: Headscale runs in a container
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Anything else?
|
|
||||||
description: |
|
|
||||||
Links? References? Anything that will give us more context about the issue you are encountering!
|
|
||||||
|
|
||||||
- Client netmap dump (see below)
|
|
||||||
- ACL configuration
|
|
||||||
- Headscale configuration
|
|
||||||
|
|
||||||
Dump the netmap of tailscale clients:
|
|
||||||
`tailscale debug netmap > DESCRIPTIVE_NAME.json`
|
|
||||||
|
|
||||||
Please provide information describing the netmap, which client, which headscale version etc.
|
|
||||||
|
|
||||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: "Feature request"
|
||||||
|
about: "Suggest an idea for headscale"
|
||||||
|
title: ""
|
||||||
|
labels: ["enhancement"]
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
We typically have a clear roadmap for what we want to improve and reserve the right
|
||||||
|
to close feature requests that does not fit in the roadmap, or fit with the scope
|
||||||
|
of the project, or we actually want to implement ourselves.
|
||||||
|
|
||||||
|
Headscale is a multinational community across the globe. Our language is English.
|
||||||
|
All bug reports needs to be in English.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
<!-- Include the reason, why you would need the feature. E.g. what problem
|
||||||
|
does it solve? Or which workflow is currently frustrating and will be improved by
|
||||||
|
this? -->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- A clear and precise description of what new or changed feature you want. -->
|
36
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
36
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@@ -1,36 +0,0 @@
|
|||||||
name: 🚀 Feature Request
|
|
||||||
description: Suggest an idea for Headscale
|
|
||||||
title: "[Feature] <title>"
|
|
||||||
labels: [enhancement]
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Use case
|
|
||||||
description: Please describe the use case for this feature.
|
|
||||||
placeholder: |
|
|
||||||
<!-- Include the reason, why you would need the feature. E.g. what problem
|
|
||||||
does it solve? Or which workflow is currently frustrating and will be improved by
|
|
||||||
this? -->
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Description
|
|
||||||
description: A clear and precise description of what new or changed feature you want.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: Contribution
|
|
||||||
description: Are you willing to contribute to the implementation of this feature?
|
|
||||||
options:
|
|
||||||
- label: I can write the design doc for this feature
|
|
||||||
required: true
|
|
||||||
- label: I can contribute this feature
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: How can it be implemented?
|
|
||||||
description: Free text for your ideas on how this feature could be implemented.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -12,7 +12,7 @@ If you find mistakes in the documentation, please submit a fix to the documentat
|
|||||||
|
|
||||||
<!-- Please tick if the following things apply. You… -->
|
<!-- Please tick if the following things apply. You… -->
|
||||||
|
|
||||||
- [ ] have read the [CONTRIBUTING.md](./CONTRIBUTING.md) file
|
- [ ] read the [CONTRIBUTING guidelines](README.md#contributing)
|
||||||
- [ ] raised a GitHub issue or discussed it on the projects chat beforehand
|
- [ ] raised a GitHub issue or discussed it on the projects chat beforehand
|
||||||
- [ ] added unit tests
|
- [ ] added unit tests
|
||||||
- [ ] added integration tests
|
- [ ] added integration tests
|
||||||
|
1
.github/workflows/test-integration.yaml
vendored
1
.github/workflows/test-integration.yaml
vendored
@@ -26,7 +26,6 @@ jobs:
|
|||||||
- TestPreAuthKeyCommand
|
- TestPreAuthKeyCommand
|
||||||
- TestPreAuthKeyCommandWithoutExpiry
|
- TestPreAuthKeyCommandWithoutExpiry
|
||||||
- TestPreAuthKeyCommandReusableEphemeral
|
- TestPreAuthKeyCommandReusableEphemeral
|
||||||
- TestPreAuthKeyCorrectUserLoggedInCommand
|
|
||||||
- TestApiKeyCommand
|
- TestApiKeyCommand
|
||||||
- TestNodeTagCommand
|
- TestNodeTagCommand
|
||||||
- TestNodeAdvertiseTagNoACLCommand
|
- TestNodeAdvertiseTagNoACLCommand
|
||||||
|
@@ -135,7 +135,7 @@ kos:
|
|||||||
- id: ghcr-debug
|
- id: ghcr-debug
|
||||||
repository: ghcr.io/juanfont/headscale
|
repository: ghcr.io/juanfont/headscale
|
||||||
bare: true
|
bare: true
|
||||||
base_image: gcr.io/distroless/base-debian12:debug
|
base_image: "debian:12"
|
||||||
build: headscale
|
build: headscale
|
||||||
main: ./cmd/headscale
|
main: ./cmd/headscale
|
||||||
env:
|
env:
|
||||||
@@ -160,7 +160,7 @@ kos:
|
|||||||
|
|
||||||
- id: dockerhub-debug
|
- id: dockerhub-debug
|
||||||
build: headscale
|
build: headscale
|
||||||
base_image: gcr.io/distroless/base-debian12:debug
|
base_image: "debian:12"
|
||||||
repository: headscale/headscale
|
repository: headscale/headscale
|
||||||
bare: true
|
bare: true
|
||||||
platforms:
|
platforms:
|
||||||
|
16
CHANGELOG.md
16
CHANGELOG.md
@@ -26,7 +26,7 @@ after improving the test harness as part of adopting [#1460](https://github.com/
|
|||||||
- Code reorganisation, a lot of code has moved, please review the following PRs accordingly [#1473](https://github.com/juanfont/headscale/pull/1473)
|
- Code reorganisation, a lot of code has moved, please review the following PRs accordingly [#1473](https://github.com/juanfont/headscale/pull/1473)
|
||||||
- Change the structure of database configuration, see [config-example.yaml](./config-example.yaml) for the new structure. [#1700](https://github.com/juanfont/headscale/pull/1700)
|
- Change the structure of database configuration, see [config-example.yaml](./config-example.yaml) for the new structure. [#1700](https://github.com/juanfont/headscale/pull/1700)
|
||||||
- Old structure has been remove and the configuration _must_ be converted.
|
- Old structure has been remove and the configuration _must_ be converted.
|
||||||
- Adds additional configuration for PostgreSQL for setting max open, idle connection and idle connection lifetime.
|
- Adds additional configuration for PostgreSQL for setting max open, idle conection and idle connection lifetime.
|
||||||
- API: Machine is now Node [#1553](https://github.com/juanfont/headscale/pull/1553)
|
- API: Machine is now Node [#1553](https://github.com/juanfont/headscale/pull/1553)
|
||||||
- Remove support for older Tailscale clients [#1611](https://github.com/juanfont/headscale/pull/1611)
|
- Remove support for older Tailscale clients [#1611](https://github.com/juanfont/headscale/pull/1611)
|
||||||
- The latest supported client is 1.38
|
- The latest supported client is 1.38
|
||||||
@@ -56,8 +56,6 @@ after improving the test harness as part of adopting [#1460](https://github.com/
|
|||||||
- Add support for deleting api keys [#1702](https://github.com/juanfont/headscale/pull/1702)
|
- Add support for deleting api keys [#1702](https://github.com/juanfont/headscale/pull/1702)
|
||||||
- Add command to backfill IP addresses for nodes missing IPs from configured prefixes. [#1869](https://github.com/juanfont/headscale/pull/1869)
|
- Add command to backfill IP addresses for nodes missing IPs from configured prefixes. [#1869](https://github.com/juanfont/headscale/pull/1869)
|
||||||
- Log available update as warning [#1877](https://github.com/juanfont/headscale/pull/1877)
|
- Log available update as warning [#1877](https://github.com/juanfont/headscale/pull/1877)
|
||||||
- Add `autogroup:internet` to Policy [#1917](https://github.com/juanfont/headscale/pull/1917)
|
|
||||||
- Restore foreign keys and add constraints [#1562](https://github.com/juanfont/headscale/pull/1562)
|
|
||||||
|
|
||||||
## 0.22.3 (2023-05-12)
|
## 0.22.3 (2023-05-12)
|
||||||
|
|
||||||
@@ -70,7 +68,7 @@ after improving the test harness as part of adopting [#1460](https://github.com/
|
|||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Add environment flags to enable pprof (profiling) [#1382](https://github.com/juanfont/headscale/pull/1382)
|
- Add environment flags to enable pprof (profiling) [#1382](https://github.com/juanfont/headscale/pull/1382)
|
||||||
- Profiles are continuously generated in our integration tests.
|
- Profiles are continously generated in our integration tests.
|
||||||
- Fix systemd service file location in `.deb` packages [#1391](https://github.com/juanfont/headscale/pull/1391)
|
- Fix systemd service file location in `.deb` packages [#1391](https://github.com/juanfont/headscale/pull/1391)
|
||||||
- Improvements on Noise implementation [#1379](https://github.com/juanfont/headscale/pull/1379)
|
- Improvements on Noise implementation [#1379](https://github.com/juanfont/headscale/pull/1379)
|
||||||
- Replace node filter logic, ensuring nodes with access can see eachother [#1381](https://github.com/juanfont/headscale/pull/1381)
|
- Replace node filter logic, ensuring nodes with access can see eachother [#1381](https://github.com/juanfont/headscale/pull/1381)
|
||||||
@@ -161,7 +159,7 @@ after improving the test harness as part of adopting [#1460](https://github.com/
|
|||||||
- SSH ACLs status:
|
- SSH ACLs status:
|
||||||
- Support `accept` and `check` (SSH can be enabled and used for connecting and authentication)
|
- Support `accept` and `check` (SSH can be enabled and used for connecting and authentication)
|
||||||
- Rejecting connections **are not supported**, meaning that if you enable SSH, then assume that _all_ `ssh` connections **will be allowed**.
|
- Rejecting connections **are not supported**, meaning that if you enable SSH, then assume that _all_ `ssh` connections **will be allowed**.
|
||||||
- If you decided to try this feature, please carefully managed permissions by blocking port `22` with regular ACLs or do _not_ set `--ssh` on your clients.
|
- If you decied to try this feature, please carefully managed permissions by blocking port `22` with regular ACLs or do _not_ set `--ssh` on your clients.
|
||||||
- We are currently improving our testing of the SSH ACLs, help us get an overview by testing and giving feedback.
|
- We are currently improving our testing of the SSH ACLs, help us get an overview by testing and giving feedback.
|
||||||
- This feature should be considered dangerous and it is disabled by default. Enable by setting `HEADSCALE_EXPERIMENTAL_FEATURE_SSH=1`.
|
- This feature should be considered dangerous and it is disabled by default. Enable by setting `HEADSCALE_EXPERIMENTAL_FEATURE_SSH=1`.
|
||||||
|
|
||||||
@@ -211,7 +209,7 @@ after improving the test harness as part of adopting [#1460](https://github.com/
|
|||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Updated dependencies (including the library that lacked armhf support) [#722](https://github.com/juanfont/headscale/pull/722)
|
- Updated dependencies (including the library that lacked armhf support) [#722](https://github.com/juanfont/headscale/pull/722)
|
||||||
- Fix missing group expansion in function `excludeCorrectlyTaggedNodes` [#563](https://github.com/juanfont/headscale/issues/563)
|
- Fix missing group expansion in function `excludeCorretlyTaggedNodes` [#563](https://github.com/juanfont/headscale/issues/563)
|
||||||
- Improve registration protocol implementation and switch to NodeKey as main identifier [#725](https://github.com/juanfont/headscale/pull/725)
|
- Improve registration protocol implementation and switch to NodeKey as main identifier [#725](https://github.com/juanfont/headscale/pull/725)
|
||||||
- Add ability to connect to PostgreSQL via unix socket [#734](https://github.com/juanfont/headscale/pull/734)
|
- Add ability to connect to PostgreSQL via unix socket [#734](https://github.com/juanfont/headscale/pull/734)
|
||||||
|
|
||||||
@@ -231,7 +229,7 @@ after improving the test harness as part of adopting [#1460](https://github.com/
|
|||||||
- Fix send on closed channel crash in polling [#542](https://github.com/juanfont/headscale/pull/542)
|
- Fix send on closed channel crash in polling [#542](https://github.com/juanfont/headscale/pull/542)
|
||||||
- Fixed spurious calls to setLastStateChangeToNow from ephemeral nodes [#566](https://github.com/juanfont/headscale/pull/566)
|
- Fixed spurious calls to setLastStateChangeToNow from ephemeral nodes [#566](https://github.com/juanfont/headscale/pull/566)
|
||||||
- Add command for moving nodes between namespaces [#362](https://github.com/juanfont/headscale/issues/362)
|
- Add command for moving nodes between namespaces [#362](https://github.com/juanfont/headscale/issues/362)
|
||||||
- Added more configuration parameters for OpenID Connect (scopes, free-form parameters, domain and user allowlist)
|
- Added more configuration parameters for OpenID Connect (scopes, free-form paramters, domain and user allowlist)
|
||||||
- Add command to set tags on a node [#525](https://github.com/juanfont/headscale/issues/525)
|
- Add command to set tags on a node [#525](https://github.com/juanfont/headscale/issues/525)
|
||||||
- Add command to view tags of nodes [#356](https://github.com/juanfont/headscale/issues/356)
|
- Add command to view tags of nodes [#356](https://github.com/juanfont/headscale/issues/356)
|
||||||
- Add --all (-a) flag to enable routes command [#360](https://github.com/juanfont/headscale/issues/360)
|
- Add --all (-a) flag to enable routes command [#360](https://github.com/juanfont/headscale/issues/360)
|
||||||
@@ -279,10 +277,10 @@ after improving the test harness as part of adopting [#1460](https://github.com/
|
|||||||
|
|
||||||
- Fix a bug were the same IP could be assigned to multiple hosts if joined in quick succession [#346](https://github.com/juanfont/headscale/pull/346)
|
- Fix a bug were the same IP could be assigned to multiple hosts if joined in quick succession [#346](https://github.com/juanfont/headscale/pull/346)
|
||||||
- Simplify the code behind registration of machines [#366](https://github.com/juanfont/headscale/pull/366)
|
- Simplify the code behind registration of machines [#366](https://github.com/juanfont/headscale/pull/366)
|
||||||
- Nodes are now only written to database if they are registered successfully
|
- Nodes are now only written to database if they are registrated successfully
|
||||||
- Fix a limitation in the ACLs that prevented users to write rules with `*` as source [#374](https://github.com/juanfont/headscale/issues/374)
|
- Fix a limitation in the ACLs that prevented users to write rules with `*` as source [#374](https://github.com/juanfont/headscale/issues/374)
|
||||||
- Reduce the overhead of marshal/unmarshal for Hostinfo, routes and endpoints by using specific types in Machine [#371](https://github.com/juanfont/headscale/pull/371)
|
- Reduce the overhead of marshal/unmarshal for Hostinfo, routes and endpoints by using specific types in Machine [#371](https://github.com/juanfont/headscale/pull/371)
|
||||||
- Apply normalization function to FQDN on hostnames when hosts registers and retrieve information [#363](https://github.com/juanfont/headscale/issues/363)
|
- Apply normalization function to FQDN on hostnames when hosts registers and retrieve informations [#363](https://github.com/juanfont/headscale/issues/363)
|
||||||
- Fix a bug that prevented the use of `tailscale logout` with OIDC [#508](https://github.com/juanfont/headscale/issues/508)
|
- Fix a bug that prevented the use of `tailscale logout` with OIDC [#508](https://github.com/juanfont/headscale/issues/508)
|
||||||
- Added Tailscale repo HEAD and unstable releases channel to the integration tests targets [#513](https://github.com/juanfont/headscale/pull/513)
|
- Added Tailscale repo HEAD and unstable releases channel to the integration tests targets [#513](https://github.com/juanfont/headscale/pull/513)
|
||||||
|
|
||||||
|
@@ -1,34 +0,0 @@
|
|||||||
# Contributing
|
|
||||||
|
|
||||||
Headscale is "Open Source, acknowledged contribution", this means that any contribution will have to be discussed with the maintainers before being added to the project.
|
|
||||||
This model has been chosen to reduce the risk of burnout by limiting the maintenance overhead of reviewing and validating third-party code.
|
|
||||||
|
|
||||||
## Why do we have this model?
|
|
||||||
|
|
||||||
Headscale has a small maintainer team that tries to balance working on the project, fixing bugs and reviewing contributions.
|
|
||||||
|
|
||||||
When we work on issues ourselves, we develop first hand knowledge of the code and it makes it possible for us to maintain and own the code as the project develops.
|
|
||||||
|
|
||||||
Code contributions are seen as a positive thing. People enjoy and engage with our project, but it also comes with some challenges; we have to understand the code, we have to understand the feature, we might have to become familiar with external libraries or services and we think about security implications. All those steps are required during the reviewing process. After the code has been merged, the feature has to be maintained. Any changes reliant on external services must be updated and expanded accordingly.
|
|
||||||
|
|
||||||
The review and day-1 maintenance adds a significant burden on the maintainers. Often we hope that the contributor will help out, but we found that most of the time, they disappear after their new feature was added.
|
|
||||||
|
|
||||||
This means that when someone contributes, we are mostly happy about it, but we do have to run it through a series of checks to establish if we actually can maintain this feature.
|
|
||||||
|
|
||||||
## What do we require?
|
|
||||||
|
|
||||||
A general description is provided here and an explicit list is provided in our pull request template.
|
|
||||||
|
|
||||||
All new features have to start out with a design document, which should be discussed on the issue tracker (not discord). It should include a use case for the feature, how it can be implemented, who will implement it and a plan for maintaining it.
|
|
||||||
|
|
||||||
All features have to be end-to-end tested (integration tests) and have good unit test coverage to ensure that they work as expected. This will also ensure that the feature continues to work as expected over time. If a change cannot be tested, a strong case for why this is not possible needs to be presented.
|
|
||||||
|
|
||||||
The contributor should help to maintain the feature over time. In case the feature is not maintained probably, the maintainers reserve themselves the right to remove features they redeem as unmaintainable. This should help to improve the quality of the software and keep it in a maintainable state.
|
|
||||||
|
|
||||||
## Bug fixes
|
|
||||||
|
|
||||||
Headscale is open to code contributions for bug fixes without discussion.
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
If you find mistakes in the documentation, please submit a fix to the documentation.
|
|
@@ -2,24 +2,31 @@
|
|||||||
# and are in no way endorsed by Headscale's maintainers as an
|
# and are in no way endorsed by Headscale's maintainers as an
|
||||||
# official nor supported release or distribution.
|
# official nor supported release or distribution.
|
||||||
|
|
||||||
FROM docker.io/golang:1.22-bookworm
|
FROM docker.io/golang:1.22-bookworm AS build
|
||||||
ARG VERSION=dev
|
ARG VERSION=dev
|
||||||
ENV GOPATH /go
|
ENV GOPATH /go
|
||||||
WORKDIR /go/src/headscale
|
WORKDIR /go/src/headscale
|
||||||
|
|
||||||
|
COPY go.mod go.sum /go/src/headscale/
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go install -ldflags="-s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=$VERSION" -a ./cmd/headscale
|
||||||
|
RUN test -e /go/bin/headscale
|
||||||
|
|
||||||
|
# Debug image
|
||||||
|
FROM docker.io/golang:1.22-bookworm
|
||||||
|
|
||||||
|
COPY --from=build /go/bin/headscale /bin/headscale
|
||||||
|
ENV TZ UTC
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install --no-install-recommends --yes less jq \
|
&& apt-get install --no-install-recommends --yes less jq \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& apt-get clean
|
&& apt-get clean
|
||||||
RUN mkdir -p /var/run/headscale
|
RUN mkdir -p /var/run/headscale
|
||||||
|
|
||||||
COPY go.mod go.sum /go/src/headscale/
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go install -ldflags="-s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=$VERSION" -a ./cmd/headscale && test -e /go/bin/headscale
|
|
||||||
|
|
||||||
# Need to reset the entrypoint or everything will run as a busybox script
|
# Need to reset the entrypoint or everything will run as a busybox script
|
||||||
ENTRYPOINT []
|
ENTRYPOINT []
|
||||||
EXPOSE 8080/tcp
|
EXPOSE 8080/tcp
|
||||||
|
@@ -1,43 +1,21 @@
|
|||||||
# Copyright (c) Tailscale Inc & AUTHORS
|
# This Dockerfile and the images produced are for testing headscale,
|
||||||
# SPDX-License-Identifier: BSD-3-Clause
|
# and are in no way endorsed by Headscale's maintainers as an
|
||||||
|
# official nor supported release or distribution.
|
||||||
|
|
||||||
# This Dockerfile is more or less lifted from tailscale/tailscale
|
FROM golang:latest
|
||||||
# to ensure a similar build process when testing the HEAD of tailscale.
|
|
||||||
|
|
||||||
FROM golang:1.22-alpine AS build-env
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y dnsutils git iptables ssh ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /go/src
|
RUN useradd --shell=/bin/bash --create-home ssh-it-user
|
||||||
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
|
|
||||||
# Replace `RUN git...` with `COPY` and a local checked out version of Tailscale in `./tailscale`
|
|
||||||
# to test specific commits of the Tailscale client. This is useful when trying to find out why
|
|
||||||
# something specific broke between two versions of Tailscale with for example `git bisect`.
|
|
||||||
# COPY ./tailscale .
|
|
||||||
RUN git clone https://github.com/tailscale/tailscale.git
|
RUN git clone https://github.com/tailscale/tailscale.git
|
||||||
|
|
||||||
WORKDIR /go/src/tailscale
|
WORKDIR /go/tailscale
|
||||||
|
|
||||||
|
RUN git checkout main \
|
||||||
# see build_docker.sh
|
&& sh build_dist.sh tailscale.com/cmd/tailscale \
|
||||||
ARG VERSION_LONG=""
|
&& sh build_dist.sh tailscale.com/cmd/tailscaled \
|
||||||
ENV VERSION_LONG=$VERSION_LONG
|
&& cp tailscale /usr/local/bin/ \
|
||||||
ARG VERSION_SHORT=""
|
&& cp tailscaled /usr/local/bin/
|
||||||
ENV VERSION_SHORT=$VERSION_SHORT
|
|
||||||
ARG VERSION_GIT_HASH=""
|
|
||||||
ENV VERSION_GIT_HASH=$VERSION_GIT_HASH
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
RUN GOARCH=$TARGETARCH go install -ldflags="\
|
|
||||||
-X tailscale.com/version.longStamp=$VERSION_LONG \
|
|
||||||
-X tailscale.com/version.shortStamp=$VERSION_SHORT \
|
|
||||||
-X tailscale.com/version.gitCommitStamp=$VERSION_GIT_HASH" \
|
|
||||||
-v ./cmd/tailscale ./cmd/tailscaled ./cmd/containerboot
|
|
||||||
|
|
||||||
FROM alpine:3.18
|
|
||||||
RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables curl
|
|
||||||
|
|
||||||
COPY --from=build-env /go/bin/* /usr/local/bin/
|
|
||||||
# For compat with the previous run.sh, although ideally you should be
|
|
||||||
# using build_docker.sh which sets an entrypoint for the image.
|
|
||||||
RUN mkdir /tailscale && ln -s /usr/local/bin/containerboot /tailscale/run.sh
|
|
||||||
|
1
Makefile
1
Makefile
@@ -31,7 +31,6 @@ test_integration:
|
|||||||
--name headscale-test-suite \
|
--name headscale-test-suite \
|
||||||
-v $$PWD:$$PWD -w $$PWD/integration \
|
-v $$PWD:$$PWD -w $$PWD/integration \
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
-v $$PWD/control_logs:/tmp/control \
|
|
||||||
golang:1 \
|
golang:1 \
|
||||||
go run gotest.tools/gotestsum@latest -- -failfast ./... -timeout 120m -parallel 8
|
go run gotest.tools/gotestsum@latest -- -failfast ./... -timeout 120m -parallel 8
|
||||||
|
|
||||||
|
12
README.md
12
README.md
@@ -95,11 +95,19 @@ The maintainers work together on setting the direction for the project. The unde
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) file.
|
Headscale is "Open Source, acknowledged contribution", this means that any
|
||||||
|
contribution will have to be discussed with the Maintainers before being submitted.
|
||||||
|
|
||||||
|
This model has been chosen to reduce the risk of burnout by limiting the
|
||||||
|
maintenance overhead of reviewing and validating third-party code.
|
||||||
|
|
||||||
|
Headscale is open to code contributions for bug fixes without discussion.
|
||||||
|
|
||||||
|
If you find mistakes in the documentation, please submit a fix to the documentation.
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
To contribute to headscale you would need the latest version of [Go](https://golang.org)
|
To contribute to headscale you would need the lastest version of [Go](https://golang.org)
|
||||||
and [Buf](https://buf.build)(Protobuf generator).
|
and [Buf](https://buf.build)(Protobuf generator).
|
||||||
|
|
||||||
We recommend using [Nix](https://nixos.org/) to setup a development environment. This can
|
We recommend using [Nix](https://nixos.org/) to setup a development environment. This can
|
||||||
|
@@ -51,11 +51,13 @@ func initConfig() {
|
|||||||
|
|
||||||
cfg, err := types.GetHeadscaleConfig()
|
cfg, err := types.GetHeadscaleConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Failed to read headscale configuration")
|
log.Fatal().Caller().Err(err).Msg("Failed to get headscale configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
machineOutput := HasMachineOutputFlag()
|
machineOutput := HasMachineOutputFlag()
|
||||||
|
|
||||||
|
zerolog.SetGlobalLevel(cfg.Log.Level)
|
||||||
|
|
||||||
// If the user has requested a "node" readable format,
|
// If the user has requested a "node" readable format,
|
||||||
// then disable login so the output remains valid.
|
// then disable login so the output remains valid.
|
||||||
if machineOutput {
|
if machineOutput {
|
||||||
|
@@ -105,7 +105,7 @@ derp:
|
|||||||
automatically_add_embedded_derp_region: true
|
automatically_add_embedded_derp_region: true
|
||||||
|
|
||||||
# For better connection stability (especially when using an Exit-Node and DNS is not working),
|
# For better connection stability (especially when using an Exit-Node and DNS is not working),
|
||||||
# it is possible to optionally add the public IPv4 and IPv6 address to the Derp-Map using:
|
# it is possible to optionall add the public IPv4 and IPv6 address to the Derp-Map using:
|
||||||
ipv4: 1.2.3.4
|
ipv4: 1.2.3.4
|
||||||
ipv6: 2001:db8::1
|
ipv6: 2001:db8::1
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ log:
|
|||||||
format: text
|
format: text
|
||||||
level: info
|
level: info
|
||||||
|
|
||||||
# Path to a file containing ACL policies.
|
# Path to a file containg ACL policies.
|
||||||
# ACLs can be defined as YAML or HUJSON.
|
# ACLs can be defined as YAML or HUJSON.
|
||||||
# https://tailscale.com/kb/1018/acls/
|
# https://tailscale.com/kb/1018/acls/
|
||||||
acl_policy_path: ""
|
acl_policy_path: ""
|
||||||
|
@@ -14,7 +14,7 @@ If the node is already registered, it can advertise exit capabilities like this:
|
|||||||
$ sudo tailscale set --advertise-exit-node
|
$ sudo tailscale set --advertise-exit-node
|
||||||
```
|
```
|
||||||
|
|
||||||
To use a node as an exit node, IP forwarding must be enabled on the node. Check the official [Tailscale documentation](https://tailscale.com/kb/1019/subnets/?tab=linux#enable-ip-forwarding) for how to enable IP forwarding.
|
To use a node as an exit node, IP forwarding must be enabled on the node. Check the official [Tailscale documentation](https://tailscale.com/kb/1019/subnets/?tab=linux#enable-ip-forwarding) for how to enable IP fowarding.
|
||||||
|
|
||||||
## On the control server
|
## On the control server
|
||||||
|
|
||||||
|
@@ -36,7 +36,7 @@ We don't know. We might be working on it. If you want to help, please send us a
|
|||||||
Please be aware that there are a number of reasons why we might not accept specific contributions:
|
Please be aware that there are a number of reasons why we might not accept specific contributions:
|
||||||
|
|
||||||
- It is not possible to implement the feature in a way that makes sense in a self-hosted environment.
|
- It is not possible to implement the feature in a way that makes sense in a self-hosted environment.
|
||||||
- Given that we are reverse-engineering Tailscale to satisfy our own curiosity, we might be interested in implementing the feature ourselves.
|
- Given that we are reverse-engineering Tailscale to satify our own curiosity, we might be interested in implementing the feature ourselves.
|
||||||
- You are not sending unit and integration tests with it.
|
- You are not sending unit and integration tests with it.
|
||||||
|
|
||||||
## Do you support Y method of deploying Headscale?
|
## Do you support Y method of deploying Headscale?
|
||||||
|
@@ -58,12 +58,12 @@ A solution could be to consider a headscale server (in it's entirety) as a
|
|||||||
tailnet.
|
tailnet.
|
||||||
|
|
||||||
For personal users the default behavior could either allow all communications
|
For personal users the default behavior could either allow all communications
|
||||||
between all namespaces (like tailscale) or disallow all communications between
|
between all namespaces (like tailscale) or dissallow all communications between
|
||||||
namespaces (current behavior).
|
namespaces (current behavior).
|
||||||
|
|
||||||
For businesses and organisations, viewing a headscale instance a single tailnet
|
For businesses and organisations, viewing a headscale instance a single tailnet
|
||||||
would allow users (namespace) to talk to each other with the ACLs. As described
|
would allow users (namespace) to talk to each other with the ACLs. As described
|
||||||
in tailscale's documentation [[1]], a server should be tagged and personal
|
in tailscale's documentation [[1]], a server should be tagged and personnal
|
||||||
devices should be tied to a user. Translated in headscale's terms each user can
|
devices should be tied to a user. Translated in headscale's terms each user can
|
||||||
have multiple devices and all those devices should be in the same namespace.
|
have multiple devices and all those devices should be in the same namespace.
|
||||||
The servers should be tagged and used as such.
|
The servers should be tagged and used as such.
|
||||||
@@ -88,7 +88,7 @@ the ability to rules in either format (HuJSON or YAML).
|
|||||||
Let's build an example use case for a small business (It may be the place where
|
Let's build an example use case for a small business (It may be the place where
|
||||||
ACL's are the most useful).
|
ACL's are the most useful).
|
||||||
|
|
||||||
We have a small company with a boss, an admin, two developer and an intern.
|
We have a small company with a boss, an admin, two developper and an intern.
|
||||||
|
|
||||||
The boss should have access to all servers but not to the users hosts. Admin
|
The boss should have access to all servers but not to the users hosts. Admin
|
||||||
should also have access to all hosts except that their permissions should be
|
should also have access to all hosts except that their permissions should be
|
||||||
@@ -173,7 +173,7 @@ need to add the following ACLs
|
|||||||
"ports": ["prod:*", "dev:*", "internal:*"]
|
"ports": ["prod:*", "dev:*", "internal:*"]
|
||||||
},
|
},
|
||||||
|
|
||||||
// admin have access to administration port (lets only consider port 22 here)
|
// admin have access to adminstration port (lets only consider port 22 here)
|
||||||
{
|
{
|
||||||
"action": "accept",
|
"action": "accept",
|
||||||
"users": ["group:admin"],
|
"users": ["group:admin"],
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
# Controlling `headscale` with remote CLI
|
# Controlling `headscale` with remote CLI
|
||||||
|
|
||||||
## Prerequisite
|
## Prerequisit
|
||||||
|
|
||||||
- A workstation to run `headscale` (could be Linux, macOS, other supported platforms)
|
- A workstation to run `headscale` (could be Linux, macOS, other supported platforms)
|
||||||
- A `headscale` server (version `0.13.0` or newer)
|
- A `headscale` server (version `0.13.0` or newer)
|
||||||
- Access to create API keys (local access to the `headscale` server)
|
- Access to create API keys (local access to the `headscale` server)
|
||||||
- `headscale` _must_ be served over TLS/HTTPS
|
- `headscale` _must_ be served over TLS/HTTPS
|
||||||
- Remote access does _not_ support unencrypted traffic.
|
- Remote access does _not_ support unencrypted traffic.
|
||||||
- Port `50443` must be open in the firewall (or port overridden by `grpc_listen_addr` option)
|
- Port `50443` must be open in the firewall (or port overriden by `grpc_listen_addr` option)
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
|
|
||||||
@@ -97,4 +97,4 @@ Checklist:
|
|||||||
- Make sure you use version `0.13.0` or newer.
|
- Make sure you use version `0.13.0` or newer.
|
||||||
- Verify that your TLS certificate is valid and trusted
|
- Verify that your TLS certificate is valid and trusted
|
||||||
- If you do not have access to a trusted certificate (e.g. from Let's Encrypt), add your self signed certificate to the trust store of your OS or
|
- If you do not have access to a trusted certificate (e.g. from Let's Encrypt), add your self signed certificate to the trust store of your OS or
|
||||||
- Set `HEADSCALE_CLI_INSECURE` to 0 in your environment
|
- Set `HEADSCALE_CLI_INSECURE` to 0 in your environement
|
||||||
|
@@ -115,7 +115,7 @@ The following Caddyfile is all that is necessary to use Caddy as a reverse proxy
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Caddy v2 will [automatically](https://caddyserver.com/docs/automatic-https) provision a certificate for your domain/subdomain, force HTTPS, and proxy websockets - no further configuration is necessary.
|
Caddy v2 will [automatically](https://caddyserver.com/docs/automatic-https) provision a certficate for your domain/subdomain, force HTTPS, and proxy websockets - no further configuration is necessary.
|
||||||
|
|
||||||
For a slightly more complex configuration which utilizes Docker containers to manage Caddy, Headscale, and Headscale-UI, [Guru Computing's guide](https://blog.gurucomputing.com.au/smart-vpns-with-headscale/) is an excellent reference.
|
For a slightly more complex configuration which utilizes Docker containers to manage Caddy, Headscale, and Headscale-UI, [Guru Computing's guide](https://blog.gurucomputing.com.au/smart-vpns-with-headscale/) is an excellent reference.
|
||||||
|
|
||||||
|
@@ -20,19 +20,17 @@ configuration (`/etc/headscale/config.yaml`).
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Download the [latest Headscale package](https://github.com/juanfont/headscale/releases/latest) for your platform (`.deb` for Ubuntu and Debian).
|
1. Download the latest Headscale package for your platform (`.deb` for Ubuntu and Debian) from [Headscale's releases page](https://github.com/juanfont/headscale/releases):
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
HEADSCALE_VERSION="" # See above URL for latest version, e.g. "X.Y.Z" (NOTE: do not add the "v" prefix!)
|
|
||||||
HEADSCALE_ARCH="" # Your system architecture, e.g. "amd64"
|
|
||||||
wget --output-document=headscale.deb \
|
wget --output-document=headscale.deb \
|
||||||
"https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_${HEADSCALE_ARCH}.deb"
|
https://github.com/juanfont/headscale/releases/download/v<HEADSCALE VERSION>/headscale_<HEADSCALE VERSION>_linux_<ARCH>.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Install Headscale:
|
1. Install Headscale:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo apt install ./headscale.deb
|
sudo apt install headscale.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Enable Headscale service, this will start Headscale at boot:
|
1. Enable Headscale service, this will start Headscale at boot:
|
||||||
|
@@ -9,17 +9,19 @@
|
|||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
|
|
||||||
This documentation has the goal of showing a user how-to install and run `headscale` on OpenBSD.
|
This documentation has the goal of showing a user how-to install and run `headscale` on OpenBSD 7.1.
|
||||||
In additional to the "get up and running section", there is an optional [rc.d section](#running-headscale-in-the-background-with-rcd)
|
In additional to the "get up and running section", there is an optional [rc.d section](#running-headscale-in-the-background-with-rcd)
|
||||||
describing how to make `headscale` run properly in a server environment.
|
describing how to make `headscale` run properly in a server environment.
|
||||||
|
|
||||||
## Install `headscale`
|
## Install `headscale`
|
||||||
|
|
||||||
1. Install from ports
|
1. Install from ports (not recommended)
|
||||||
|
|
||||||
You can install headscale from ports by running `pkg_add headscale`.
|
!!! info
|
||||||
|
|
||||||
1. Install from source
|
As of OpenBSD 7.2, there's a headscale in ports collection, however, it's severely outdated(v0.12.4). You can install it via `pkg_add headscale`.
|
||||||
|
|
||||||
|
1. Install from source on OpenBSD 7.2
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# Install prerequistes
|
# Install prerequistes
|
||||||
@@ -30,7 +32,7 @@ describing how to make `headscale` run properly in a server environment.
|
|||||||
cd headscale
|
cd headscale
|
||||||
|
|
||||||
# optionally checkout a release
|
# optionally checkout a release
|
||||||
# option a. you can find official release at https://github.com/juanfont/headscale/releases/latest
|
# option a. you can find offical relase at https://github.com/juanfont/headscale/releases/latest
|
||||||
# option b. get latest tag, this may be a beta release
|
# option b. get latest tag, this may be a beta release
|
||||||
latestTag=$(git describe --tags `git rev-list --tags --max-count=1`)
|
latestTag=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||||
|
|
||||||
@@ -57,7 +59,7 @@ describing how to make `headscale` run properly in a server environment.
|
|||||||
cd headscale
|
cd headscale
|
||||||
|
|
||||||
# optionally checkout a release
|
# optionally checkout a release
|
||||||
# option a. you can find official release at https://github.com/juanfont/headscale/releases/latest
|
# option a. you can find offical relase at https://github.com/juanfont/headscale/releases/latest
|
||||||
# option b. get latest tag, this may be a beta release
|
# option b. get latest tag, this may be a beta release
|
||||||
latestTag=$(git describe --tags `git rev-list --tags --max-count=1`)
|
latestTag=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||||
|
|
||||||
|
5
examples/README.md
Normal file
5
examples/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Examples
|
||||||
|
|
||||||
|
This directory contains examples on how to run `headscale` on different platforms.
|
||||||
|
|
||||||
|
All examples are provided by the community and they are not verified by the `headscale` authors.
|
2
examples/kustomize/.gitignore
vendored
Normal file
2
examples/kustomize/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/**/site
|
||||||
|
/**/secrets
|
100
examples/kustomize/README.md
Normal file
100
examples/kustomize/README.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Deploying headscale on Kubernetes
|
||||||
|
|
||||||
|
**Note:** This is contributed by the community and not verified by the headscale authors.
|
||||||
|
|
||||||
|
This directory contains [Kustomize](https://kustomize.io) templates that deploy
|
||||||
|
headscale in various configurations.
|
||||||
|
|
||||||
|
These templates currently support Rancher k3s. Other clusters may require
|
||||||
|
adaptation, especially around volume claims and ingress.
|
||||||
|
|
||||||
|
Commands below assume this directory is your current working directory.
|
||||||
|
|
||||||
|
# Generate secrets and site configuration
|
||||||
|
|
||||||
|
Run `./init.bash` to generate keys, passwords, and site configuration files.
|
||||||
|
|
||||||
|
Edit `base/site/public.env`, changing `public-hostname` to the public DNS name
|
||||||
|
that will be used for your headscale deployment.
|
||||||
|
|
||||||
|
Set `public-proto` to "https" if you're planning to use TLS & Let's Encrypt.
|
||||||
|
|
||||||
|
Configure DERP servers by editing `base/site/derp.yaml` if needed.
|
||||||
|
|
||||||
|
# Add the image to the registry
|
||||||
|
|
||||||
|
You'll somehow need to get `headscale:latest` into your cluster image registry.
|
||||||
|
|
||||||
|
An easy way to do this with k3s:
|
||||||
|
|
||||||
|
- Reconfigure k3s to use docker instead of containerd (`k3s server --docker`)
|
||||||
|
- `docker build -t headscale:latest ..` from here
|
||||||
|
|
||||||
|
# Create the namespace
|
||||||
|
|
||||||
|
If it doesn't already exist, `kubectl create ns headscale`.
|
||||||
|
|
||||||
|
# Deploy headscale
|
||||||
|
|
||||||
|
## sqlite
|
||||||
|
|
||||||
|
`kubectl -n headscale apply -k ./sqlite`
|
||||||
|
|
||||||
|
## postgres
|
||||||
|
|
||||||
|
`kubectl -n headscale apply -k ./postgres`
|
||||||
|
|
||||||
|
# TLS & Let's Encrypt
|
||||||
|
|
||||||
|
Test a staging certificate with your configured DNS name and Let's Encrypt.
|
||||||
|
|
||||||
|
`kubectl -n headscale apply -k ./staging-tls`
|
||||||
|
|
||||||
|
Replace with a production certificate.
|
||||||
|
|
||||||
|
`kubectl -n headscale apply -k ./production-tls`
|
||||||
|
|
||||||
|
## Static / custom TLS certificates
|
||||||
|
|
||||||
|
Only Let's Encrypt is supported. If you need other TLS settings, modify or patch the ingress.
|
||||||
|
|
||||||
|
# Administration
|
||||||
|
|
||||||
|
Use the wrapper script to remotely operate headscale to perform administrative
|
||||||
|
tasks like creating namespaces, authkeys, etc.
|
||||||
|
|
||||||
|
```
|
||||||
|
[c@nix-slate:~/Projects/headscale/k8s]$ ./headscale.bash
|
||||||
|
|
||||||
|
headscale is an open source implementation of the Tailscale control server
|
||||||
|
|
||||||
|
https://github.com/juanfont/headscale
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
headscale [command]
|
||||||
|
|
||||||
|
Available Commands:
|
||||||
|
help Help about any command
|
||||||
|
namespace Manage the namespaces of headscale
|
||||||
|
node Manage the nodes of headscale
|
||||||
|
preauthkey Handle the preauthkeys in headscale
|
||||||
|
routes Manage the routes of headscale
|
||||||
|
serve Launches the headscale server
|
||||||
|
version Print the version.
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for headscale
|
||||||
|
-o, --output string Output format. Empty for human-readable, 'json' or 'json-line'
|
||||||
|
|
||||||
|
Use "headscale [command] --help" for more information about a command.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
# TODO / Ideas
|
||||||
|
|
||||||
|
- Interpolate `email:` option to the ClusterIssuer from site configuration.
|
||||||
|
This probably needs to be done with a transformer, kustomize vars don't seem to work.
|
||||||
|
- Add kustomize examples for cloud-native ingress, load balancer
|
||||||
|
- CockroachDB for the backend
|
||||||
|
- DERP server deployment
|
||||||
|
- Tor hidden service
|
9
examples/kustomize/base/configmap.yaml
Normal file
9
examples/kustomize/base/configmap.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: headscale-config
|
||||||
|
data:
|
||||||
|
server_url: $(PUBLIC_PROTO)://$(PUBLIC_HOSTNAME)
|
||||||
|
listen_addr: "0.0.0.0:8080"
|
||||||
|
metrics_listen_addr: "127.0.0.1:9090"
|
||||||
|
ephemeral_node_inactivity_timeout: "30m"
|
18
examples/kustomize/base/ingress.yaml
Normal file
18
examples/kustomize/base/ingress.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: headscale
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: traefik
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: $(PUBLIC_HOSTNAME)
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
service:
|
||||||
|
name: headscale
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
path: /
|
||||||
|
pathType: Prefix
|
42
examples/kustomize/base/kustomization.yaml
Normal file
42
examples/kustomize/base/kustomization.yaml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
namespace: headscale
|
||||||
|
resources:
|
||||||
|
- configmap.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- service.yaml
|
||||||
|
generatorOptions:
|
||||||
|
disableNameSuffixHash: true
|
||||||
|
configMapGenerator:
|
||||||
|
- name: headscale-site
|
||||||
|
files:
|
||||||
|
- derp.yaml=site/derp.yaml
|
||||||
|
envs:
|
||||||
|
- site/public.env
|
||||||
|
- name: headscale-etc
|
||||||
|
literals:
|
||||||
|
- config.json={}
|
||||||
|
secretGenerator:
|
||||||
|
- name: headscale
|
||||||
|
files:
|
||||||
|
- secrets/private-key
|
||||||
|
vars:
|
||||||
|
- name: PUBLIC_PROTO
|
||||||
|
objRef:
|
||||||
|
kind: ConfigMap
|
||||||
|
name: headscale-site
|
||||||
|
apiVersion: v1
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: data.public-proto
|
||||||
|
- name: PUBLIC_HOSTNAME
|
||||||
|
objRef:
|
||||||
|
kind: ConfigMap
|
||||||
|
name: headscale-site
|
||||||
|
apiVersion: v1
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: data.public-hostname
|
||||||
|
- name: CONTACT_EMAIL
|
||||||
|
objRef:
|
||||||
|
kind: ConfigMap
|
||||||
|
name: headscale-site
|
||||||
|
apiVersion: v1
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: data.contact-email
|
13
examples/kustomize/base/service.yaml
Normal file
13
examples/kustomize/base/service.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: headscale
|
||||||
|
labels:
|
||||||
|
app: headscale
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: headscale
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
targetPort: http
|
||||||
|
port: 8080
|
3
examples/kustomize/headscale.bash
Executable file
3
examples/kustomize/headscale.bash
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eu
|
||||||
|
exec kubectl -n headscale exec -ti pod/headscale-0 -- /go/bin/headscale "$@"
|
22
examples/kustomize/init.bash
Executable file
22
examples/kustomize/init.bash
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eux
|
||||||
|
cd $(dirname $0)
|
||||||
|
|
||||||
|
umask 022
|
||||||
|
mkdir -p base/site/
|
||||||
|
[ ! -e base/site/public.env ] && (
|
||||||
|
cat >base/site/public.env <<EOF
|
||||||
|
public-hostname=localhost
|
||||||
|
public-proto=http
|
||||||
|
contact-email=headscale@example.com
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
[ ! -e base/site/derp.yaml ] && cp ../derp.yaml base/site/derp.yaml
|
||||||
|
|
||||||
|
umask 077
|
||||||
|
mkdir -p base/secrets/
|
||||||
|
[ ! -e base/secrets/private-key ] && (
|
||||||
|
wg genkey > base/secrets/private-key
|
||||||
|
)
|
||||||
|
mkdir -p postgres/secrets/
|
||||||
|
[ ! -e postgres/secrets/password ] && (head -c 32 /dev/urandom | base64 -w0 > postgres/secrets/password)
|
3
examples/kustomize/install-cert-manager.bash
Executable file
3
examples/kustomize/install-cert-manager.bash
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eux
|
||||||
|
kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.4.0/cert-manager.yaml
|
81
examples/kustomize/postgres/deployment.yaml
Normal file
81
examples/kustomize/postgres/deployment.yaml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: headscale
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: headscale
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: headscale
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: headscale
|
||||||
|
image: "headscale:latest"
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: ["/go/bin/headscale", "serve"]
|
||||||
|
env:
|
||||||
|
- name: SERVER_URL
|
||||||
|
value: $(PUBLIC_PROTO)://$(PUBLIC_HOSTNAME)
|
||||||
|
- name: LISTEN_ADDR
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: headscale-config
|
||||||
|
key: listen_addr
|
||||||
|
- name: METRICS_LISTEN_ADDR
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: headscale-config
|
||||||
|
key: metrics_listen_addr
|
||||||
|
- name: DERP_MAP_PATH
|
||||||
|
value: /vol/config/derp.yaml
|
||||||
|
- name: EPHEMERAL_NODE_INACTIVITY_TIMEOUT
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: headscale-config
|
||||||
|
key: ephemeral_node_inactivity_timeout
|
||||||
|
- name: DB_TYPE
|
||||||
|
value: postgres
|
||||||
|
- name: DB_HOST
|
||||||
|
value: postgres.headscale.svc.cluster.local
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "5432"
|
||||||
|
- name: DB_USER
|
||||||
|
value: headscale
|
||||||
|
- name: DB_PASS
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: postgresql
|
||||||
|
key: password
|
||||||
|
- name: DB_NAME
|
||||||
|
value: headscale
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
protocol: TCP
|
||||||
|
containerPort: 8080
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 15
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /vol/config
|
||||||
|
- name: secret
|
||||||
|
mountPath: /vol/secret
|
||||||
|
- name: etc
|
||||||
|
mountPath: /etc/headscale
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: headscale-site
|
||||||
|
- name: etc
|
||||||
|
configMap:
|
||||||
|
name: headscale-etc
|
||||||
|
- name: secret
|
||||||
|
secret:
|
||||||
|
secretName: headscale
|
13
examples/kustomize/postgres/kustomization.yaml
Normal file
13
examples/kustomize/postgres/kustomization.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace: headscale
|
||||||
|
bases:
|
||||||
|
- ../base
|
||||||
|
resources:
|
||||||
|
- deployment.yaml
|
||||||
|
- postgres-service.yaml
|
||||||
|
- postgres-statefulset.yaml
|
||||||
|
generatorOptions:
|
||||||
|
disableNameSuffixHash: true
|
||||||
|
secretGenerator:
|
||||||
|
- name: postgresql
|
||||||
|
files:
|
||||||
|
- secrets/password
|
13
examples/kustomize/postgres/postgres-service.yaml
Normal file
13
examples/kustomize/postgres/postgres-service.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
labels:
|
||||||
|
app: postgres
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: postgres
|
||||||
|
ports:
|
||||||
|
- name: postgres
|
||||||
|
targetPort: postgres
|
||||||
|
port: 5432
|
49
examples/kustomize/postgres/postgres-statefulset.yaml
Normal file
49
examples/kustomize/postgres/postgres-statefulset.yaml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
spec:
|
||||||
|
serviceName: postgres
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: postgres
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: postgres
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
image: "postgres:13"
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: postgresql
|
||||||
|
key: password
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: headscale
|
||||||
|
ports:
|
||||||
|
- name: postgres
|
||||||
|
protocol: TCP
|
||||||
|
containerPort: 5432
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 5432
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 15
|
||||||
|
volumeMounts:
|
||||||
|
- name: pgdata
|
||||||
|
mountPath: /var/lib/postgresql/data
|
||||||
|
volumeClaimTemplates:
|
||||||
|
- metadata:
|
||||||
|
name: pgdata
|
||||||
|
spec:
|
||||||
|
storageClassName: local-path
|
||||||
|
accessModes: ["ReadWriteOnce"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
11
examples/kustomize/production-tls/ingress-patch.yaml
Normal file
11
examples/kustomize/production-tls/ingress-patch.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: headscale
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-production
|
||||||
|
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||||
|
spec:
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- $(PUBLIC_HOSTNAME)
|
||||||
|
secretName: production-cert
|
9
examples/kustomize/production-tls/kustomization.yaml
Normal file
9
examples/kustomize/production-tls/kustomization.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace: headscale
|
||||||
|
bases:
|
||||||
|
- ../base
|
||||||
|
resources:
|
||||||
|
- production-issuer.yaml
|
||||||
|
patches:
|
||||||
|
- path: ingress-patch.yaml
|
||||||
|
target:
|
||||||
|
kind: Ingress
|
16
examples/kustomize/production-tls/production-issuer.yaml
Normal file
16
examples/kustomize/production-tls/production-issuer.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt-production
|
||||||
|
spec:
|
||||||
|
acme:
|
||||||
|
# TODO: figure out how to get kustomize to interpolate this, or use a transformer
|
||||||
|
#email: $(CONTACT_EMAIL)
|
||||||
|
server: https://acme-v02.api.letsencrypt.org/directory
|
||||||
|
privateKeySecretRef:
|
||||||
|
# Secret resource used to store the account's private key.
|
||||||
|
name: letsencrypt-production-acc-key
|
||||||
|
solvers:
|
||||||
|
- http01:
|
||||||
|
ingress:
|
||||||
|
class: traefik
|
5
examples/kustomize/sqlite/kustomization.yaml
Normal file
5
examples/kustomize/sqlite/kustomization.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace: headscale
|
||||||
|
bases:
|
||||||
|
- ../base
|
||||||
|
resources:
|
||||||
|
- statefulset.yaml
|
82
examples/kustomize/sqlite/statefulset.yaml
Normal file
82
examples/kustomize/sqlite/statefulset.yaml
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: headscale
|
||||||
|
spec:
|
||||||
|
serviceName: headscale
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: headscale
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: headscale
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: headscale
|
||||||
|
image: "headscale:latest"
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: ["/go/bin/headscale", "serve"]
|
||||||
|
env:
|
||||||
|
- name: SERVER_URL
|
||||||
|
value: $(PUBLIC_PROTO)://$(PUBLIC_HOSTNAME)
|
||||||
|
- name: LISTEN_ADDR
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: headscale-config
|
||||||
|
key: listen_addr
|
||||||
|
- name: METRICS_LISTEN_ADDR
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: headscale-config
|
||||||
|
key: metrics_listen_addr
|
||||||
|
- name: DERP_MAP_PATH
|
||||||
|
value: /vol/config/derp.yaml
|
||||||
|
- name: EPHEMERAL_NODE_INACTIVITY_TIMEOUT
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: headscale-config
|
||||||
|
key: ephemeral_node_inactivity_timeout
|
||||||
|
- name: DB_TYPE
|
||||||
|
value: sqlite3
|
||||||
|
- name: DB_PATH
|
||||||
|
value: /vol/data/db.sqlite
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
protocol: TCP
|
||||||
|
containerPort: 8080
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 15
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /vol/config
|
||||||
|
- name: data
|
||||||
|
mountPath: /vol/data
|
||||||
|
- name: secret
|
||||||
|
mountPath: /vol/secret
|
||||||
|
- name: etc
|
||||||
|
mountPath: /etc/headscale
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: headscale-site
|
||||||
|
- name: etc
|
||||||
|
configMap:
|
||||||
|
name: headscale-etc
|
||||||
|
- name: secret
|
||||||
|
secret:
|
||||||
|
secretName: headscale
|
||||||
|
volumeClaimTemplates:
|
||||||
|
- metadata:
|
||||||
|
name: data
|
||||||
|
spec:
|
||||||
|
storageClassName: local-path
|
||||||
|
accessModes: ["ReadWriteOnce"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
11
examples/kustomize/staging-tls/ingress-patch.yaml
Normal file
11
examples/kustomize/staging-tls/ingress-patch.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: headscale
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-staging
|
||||||
|
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||||
|
spec:
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- $(PUBLIC_HOSTNAME)
|
||||||
|
secretName: staging-cert
|
9
examples/kustomize/staging-tls/kustomization.yaml
Normal file
9
examples/kustomize/staging-tls/kustomization.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace: headscale
|
||||||
|
bases:
|
||||||
|
- ../base
|
||||||
|
resources:
|
||||||
|
- staging-issuer.yaml
|
||||||
|
patches:
|
||||||
|
- path: ingress-patch.yaml
|
||||||
|
target:
|
||||||
|
kind: Ingress
|
16
examples/kustomize/staging-tls/staging-issuer.yaml
Normal file
16
examples/kustomize/staging-tls/staging-issuer.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt-staging
|
||||||
|
spec:
|
||||||
|
acme:
|
||||||
|
# TODO: figure out how to get kustomize to interpolate this, or use a transformer
|
||||||
|
#email: $(CONTACT_EMAIL)
|
||||||
|
server: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||||
|
privateKeySecretRef:
|
||||||
|
# Secret resource used to store the account's private key.
|
||||||
|
name: letsencrypt-staging-acc-key
|
||||||
|
solvers:
|
||||||
|
- http01:
|
||||||
|
ingress:
|
||||||
|
class: traefik
|
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1715774670,
|
"lastModified": 1712883908,
|
||||||
"narHash": "sha256-iJYnKMtLi5u6hZhJm94cRNSDG5Rz6ZzIkGbhPFtDRm0=",
|
"narHash": "sha256-icE1IJE9fHcbDfJ0+qWoDdcBXUoZCcIJxME4lMHwvSM=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "b3fcfcfabd01b947a1e4f36622bbffa3985bdac6",
|
"rev": "a0c9e3aee1000ac2bfb0e5b98c94c946a5d180a9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
# When updating go.mod or go.sum, a new sha will need to be calculated,
|
# 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.
|
# update this if you have a mismatch after doing a change to thos files.
|
||||||
vendorHash = "sha256-EorT2AVwA3usly/LcNor6r5UIhLCdj3L4O4ilgTIC2o=";
|
vendorHash = "sha256-HGu/OCtjzPeBki5FSL6v1XivCJ30eqj9rL0x7ZVv1TM=";
|
||||||
|
|
||||||
subPackages = ["cmd/headscale"];
|
subPackages = ["cmd/headscale"];
|
||||||
|
|
||||||
|
116
go.mod
116
go.mod
@@ -1,55 +1,54 @@
|
|||||||
module github.com/juanfont/headscale
|
module github.com/juanfont/headscale
|
||||||
|
|
||||||
go 1.22.0
|
go 1.22
|
||||||
|
|
||||||
toolchain go1.22.2
|
toolchain go1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
github.com/coreos/go-oidc/v3 v3.10.0
|
github.com/coreos/go-oidc/v3 v3.9.0
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||||
github.com/deckarep/golang-set/v2 v2.6.0
|
github.com/deckarep/golang-set/v2 v2.6.0
|
||||||
github.com/glebarez/sqlite v1.11.0
|
github.com/glebarez/sqlite v1.10.0
|
||||||
github.com/go-gormigrate/gormigrate/v2 v2.1.2
|
github.com/go-gormigrate/gormigrate/v2 v2.1.1
|
||||||
github.com/gofrs/uuid/v5 v5.2.0
|
github.com/gofrs/uuid/v5 v5.0.0
|
||||||
github.com/google/go-cmp v0.6.0
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
|
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1
|
||||||
github.com/jagottsicher/termcolor v1.0.2
|
github.com/jagottsicher/termcolor v1.0.2
|
||||||
github.com/klauspost/compress v1.17.8
|
github.com/klauspost/compress v1.17.6
|
||||||
github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
|
github.com/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282
|
||||||
github.com/ory/dockertest/v3 v3.10.0
|
github.com/ory/dockertest/v3 v3.10.0
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/philip-bui/grpc-zerolog v1.0.1
|
github.com/philip-bui/grpc-zerolog v1.0.1
|
||||||
github.com/pkg/profile v1.7.0
|
github.com/pkg/profile v1.7.0
|
||||||
github.com/prometheus/client_golang v1.18.0
|
github.com/prometheus/client_golang v1.18.0
|
||||||
github.com/prometheus/common v0.46.0
|
github.com/prometheus/common v0.46.0
|
||||||
github.com/pterm/pterm v0.12.79
|
github.com/pterm/pterm v0.12.78
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.1.0
|
github.com/puzpuzpuz/xsync/v3 v3.0.2
|
||||||
github.com/rs/zerolog v1.32.0
|
github.com/rs/zerolog v1.32.0
|
||||||
github.com/samber/lo v1.39.0
|
github.com/samber/lo v1.39.0
|
||||||
github.com/sasha-s/go-deadlock v0.3.1
|
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/spf13/viper v1.18.2
|
github.com/spf13/viper v1.18.2
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
|
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
|
||||||
github.com/tailscale/tailsql v0.0.0-20240418235827-820559f382c1
|
github.com/tailscale/tailsql v0.0.0-20231216172832-51483e0c711b
|
||||||
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
|
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.23.0
|
golang.org/x/crypto v0.21.0
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3
|
||||||
golang.org/x/net v0.25.0
|
golang.org/x/net v0.22.0
|
||||||
golang.org/x/oauth2 v0.20.0
|
golang.org/x/oauth2 v0.17.0
|
||||||
golang.org/x/sync v0.7.0
|
golang.org/x/sync v0.6.0
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240515191416-fc5f0ca64291
|
google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014
|
||||||
google.golang.org/grpc v1.64.0
|
google.golang.org/grpc v1.61.0
|
||||||
google.golang.org/protobuf v1.34.1
|
google.golang.org/protobuf v1.32.0
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gorm.io/driver/postgres v1.5.7
|
gorm.io/driver/postgres v1.5.4
|
||||||
gorm.io/gorm v1.25.10
|
gorm.io/gorm v1.25.5
|
||||||
tailscale.com v1.66.3
|
tailscale.com v1.58.2
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -59,7 +58,7 @@ require (
|
|||||||
dario.cat/mergo v1.0.0 // indirect
|
dario.cat/mergo v1.0.0 // indirect
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||||
github.com/akutz/memconn v0.1.0 // indirect
|
github.com/akutz/memconn v0.1.0 // indirect
|
||||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
|
||||||
@@ -78,39 +77,35 @@ require (
|
|||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.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/smithy-go v1.19.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // 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.2.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/containerd/console v1.0.4 // indirect
|
github.com/containerd/console v1.0.4 // indirect
|
||||||
github.com/containerd/continuity v0.4.3 // indirect
|
github.com/containerd/continuity v0.4.3 // indirect
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
github.com/coreos/go-iptables v0.7.0 // indirect
|
||||||
github.com/creachadair/mds v0.14.5 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
github.com/dblohm7/wingoes v0.0.0-20240123200102-b75a8a7d7eb0 // indirect
|
github.com/dblohm7/wingoes v0.0.0-20240123200102-b75a8a7d7eb0 // indirect
|
||||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
|
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
|
||||||
github.com/docker/cli v26.1.3+incompatible // indirect
|
github.com/docker/cli v25.0.3+incompatible // indirect
|
||||||
github.com/docker/docker v26.1.3+incompatible // indirect
|
github.com/docker/docker v25.0.3+incompatible // indirect
|
||||||
github.com/docker/go-connections v0.5.0 // indirect
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/felixge/fgprof v0.9.4 // indirect
|
github.com/felixge/fgprof v0.9.3 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.5.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.5.0 // indirect
|
||||||
github.com/gaissmai/bart v0.4.1 // indirect
|
|
||||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||||
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
|
github.com/go-jose/go-jose/v3 v3.0.1 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
|
||||||
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // indirect
|
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/google/go-github v17.0.0+incompatible // indirect
|
github.com/google/go-github v17.0.0+incompatible // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
|
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c // indirect
|
||||||
github.com/google/pprof v0.0.0-20240509144519-723abb6459b7 // indirect
|
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/gookit/color v1.5.4 // indirect
|
github.com/gookit/color v1.5.4 // indirect
|
||||||
@@ -124,7 +119,7 @@ require (
|
|||||||
github.com/insomniacslk/dhcp v0.0.0-20240129002554-15c9b8791914 // indirect
|
github.com/insomniacslk/dhcp v0.0.0-20240129002554-15c9b8791914 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
github.com/jackc/pgx/v5 v5.5.3 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
@@ -149,14 +144,12 @@ require (
|
|||||||
github.com/miekg/dns v1.1.58 // indirect
|
github.com/miekg/dns v1.1.58 // indirect
|
||||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
|
||||||
github.com/moby/term v0.5.0 // indirect
|
github.com/moby/term v0.5.0 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
github.com/opencontainers/image-spec v1.1.0-rc6 // indirect
|
||||||
github.com/opencontainers/runc v1.1.12 // indirect
|
github.com/opencontainers/runc v1.1.12 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
|
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
@@ -173,17 +166,16 @@ require (
|
|||||||
github.com/spf13/afero v1.11.0 // indirect
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
github.com/spf13/cast v1.6.0 // indirect
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.1 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
|
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
|
||||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
|
||||||
github.com/tailscale/golang-x-crypto v0.0.0-20240108194725-7ce1f622c780 // indirect
|
github.com/tailscale/golang-x-crypto v0.0.0-20240108194725-7ce1f622c780 // indirect
|
||||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
|
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
|
||||||
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 // indirect
|
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 // indirect
|
||||||
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 // indirect
|
github.com/tailscale/setec v0.0.0-20240102233422-ba738f8ab5a0 // indirect
|
||||||
github.com/tailscale/setec v0.0.0-20240314234648-9da8e7407257 // indirect
|
github.com/tailscale/web-client-prebuilt v0.0.0-20240111230031-5ca22df9e6e7 // indirect
|
||||||
github.com/tailscale/squibble v0.0.0-20240418235321-9ee0eeb78185 // indirect
|
github.com/tailscale/wireguard-go v0.0.0-20231121184858-cc193a0b3272 // indirect
|
||||||
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect
|
|
||||||
github.com/tailscale/wireguard-go v0.0.0-20240429185444-03c5a0ccf754 // indirect
|
|
||||||
github.com/tcnksm/go-httpstat v0.2.0 // indirect
|
github.com/tcnksm/go-httpstat v0.2.0 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
|
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2 // indirect
|
github.com/vishvananda/netlink v1.2.1-beta.2 // indirect
|
||||||
@@ -195,21 +187,25 @@ require (
|
|||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
|
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
|
||||||
golang.org/x/mod v0.17.0 // indirect
|
golang.org/x/mod v0.16.0 // indirect
|
||||||
golang.org/x/sys v0.20.0 // indirect
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
golang.org/x/term v0.20.0 // indirect
|
golang.org/x/term v0.18.0 // indirect
|
||||||
golang.org/x/text v0.15.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
golang.org/x/tools v0.21.0 // indirect
|
golang.org/x/tools v0.19.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3 // indirect
|
gvisor.dev/gvisor v0.0.0-20230928000133-4fe30062272c // indirect
|
||||||
modernc.org/libc v1.50.6 // indirect
|
inet.af/peercred v0.0.0-20210906144145-0893ea02156a // indirect
|
||||||
|
modernc.org/libc v1.49.3 // indirect
|
||||||
modernc.org/mathutil v1.6.0 // indirect
|
modernc.org/mathutil v1.6.0 // indirect
|
||||||
modernc.org/memory v1.8.0 // indirect
|
modernc.org/memory v1.8.0 // indirect
|
||||||
modernc.org/sqlite v1.29.9 // indirect
|
modernc.org/sqlite v1.28.0 // indirect
|
||||||
nhooyr.io/websocket v1.8.10 // indirect
|
nhooyr.io/websocket v1.8.10 // indirect
|
||||||
)
|
)
|
||||||
|
319
go.sum
319
go.sum
@@ -29,8 +29,8 @@ github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/
|
|||||||
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
|
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
|
||||||
github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4=
|
github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4=
|
||||||
github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY=
|
github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||||
@@ -83,22 +83,14 @@ github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6
|
|||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||||
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
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=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
|
||||||
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
|
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
|
||||||
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
|
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
@@ -108,18 +100,17 @@ github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn
|
|||||||
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||||
github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8=
|
github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8=
|
||||||
github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
|
github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
|
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||||
github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU=
|
github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo=
|
||||||
github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac=
|
github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
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.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
@@ -130,49 +121,38 @@ github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80N
|
|||||||
github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
|
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
|
||||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
|
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/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284=
|
||||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/cli v26.1.3+incompatible h1:bUpXT/N0kDE3VUHI2r5VMsYQgi38kYuoC0oL9yt3lqc=
|
github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ=
|
||||||
github.com/docker/cli v26.1.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo=
|
|
||||||
github.com/docker/docker v26.1.3+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 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
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=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI=
|
|
||||||
github.com/dsnet/try v0.0.3/go.mod h1:WBM8tRpUmnXXhY1U6/S8dt6UWdHTQ7y8A5YSkRCkq40=
|
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||||
github.com/felixge/fgprof v0.9.4 h1:ocDNwMFlnA0NU0zSB3I52xkO4sFXk80VK9lXjLClu88=
|
|
||||||
github.com/felixge/fgprof v0.9.4/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
|
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
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/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 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
|
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
|
||||||
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||||
github.com/gaissmai/bart v0.4.1 h1:G1t58voWkNmT47lBDawH5QhtTDsdqRIO+ftq5x4P9Ls=
|
|
||||||
github.com/gaissmai/bart v0.4.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
|
|
||||||
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
||||||
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
||||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
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.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
|
||||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
|
||||||
github.com/go-gormigrate/gormigrate/v2 v2.1.2 h1:F/d1hpHbRAvKezziV2CC5KUE82cVe9zTgHSBoOOZ4CY=
|
github.com/go-gormigrate/gormigrate/v2 v2.1.1 h1:eGS0WTFRV30r103lU8JNXY27KbviRnqqIDobW3EV3iY=
|
||||||
github.com/go-gormigrate/gormigrate/v2 v2.1.2/go.mod h1:9nHVX6z3FCMCQPA7PThGcA55t22yKQfK/Dnsf5i7hUo=
|
github.com/go-gormigrate/gormigrate/v2 v2.1.1/go.mod h1:L7nJ620PFDKei9QOhJzqA8kRCk+E3UbV2f5gv+1ndLc=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
|
github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||||
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
|
|
||||||
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
|
||||||
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg=
|
|
||||||
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-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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
@@ -180,18 +160,15 @@ github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW
|
|||||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
|
||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
|
||||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
|
||||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
||||||
github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM=
|
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
||||||
github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
@@ -199,13 +176,17 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
|
|||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||||
@@ -214,12 +195,11 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
|
|||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
|
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c h1:06RMfw+TMMHtRuUOroMeatRCCgSMWXCJQeABvHU69YQ=
|
||||||
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
|
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c/go.mod h1:BVIYo3cdnT4qSylnYqcd5YtmXhr51cJPGtnLBe/uLBU=
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
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-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=
|
||||||
github.com/google/pprof v0.0.0-20240509144519-723abb6459b7 h1:velgFPYr1X9TDwLIfkV7fWqsFlf7TeP11M/7kPd/dVI=
|
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
github.com/google/pprof v0.0.0-20240509144519-723abb6459b7/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
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/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@@ -236,13 +216,10 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX
|
|||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
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 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/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
|
||||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
||||||
@@ -250,7 +227,6 @@ github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3s
|
|||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
|
||||||
github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio=
|
github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio=
|
||||||
github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE=
|
github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
@@ -261,14 +237,12 @@ 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/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
|
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s=
|
||||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
github.com/jackc/pgx/v5 v5.5.3/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
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/puddle/v2 v2.2.1/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 h1:fo0c51pQSuLBN1+yVX2ZE+hE+P7ULb/TY8eRowJnrsM=
|
||||||
github.com/jagottsicher/termcolor v1.0.2/go.mod h1:RcH8uFwF/0wbEdQmi83rjmlJ+QOKdMSE9Rc1BEB7zFo=
|
github.com/jagottsicher/termcolor v1.0.2/go.mod h1:RcH8uFwF/0wbEdQmi83rjmlJ+QOKdMSE9Rc1BEB7zFo=
|
||||||
github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g=
|
|
||||||
github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
|
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
@@ -277,7 +251,6 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
|||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
|
||||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
|
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
|
||||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8=
|
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8=
|
||||||
@@ -287,13 +260,13 @@ 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/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/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
|
||||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
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.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
|
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
|
||||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
|
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
|
||||||
@@ -307,14 +280,12 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
|
||||||
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
||||||
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
@@ -343,32 +314,27 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc
|
|||||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
|
||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
|
||||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 h1:9bCMuD3TcnjeqjPT2gSlha4asp8NvgcFRYExCaikCxk=
|
github.com/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282 h1:TQMyrpijtkFyXpNI3rY5hsZQZw+paiH+BfAlsb81HBY=
|
||||||
github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25/go.mod h1:eDjgYHYDJbPLBLsyZ6qRaugP0mX8vePOhZ5id1fdzJw=
|
github.com/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282/go.mod h1:rW25Kyd08Wdn3UVn0YBsDTSvReu0jqpmJKzxITPSjks=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
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-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU=
|
||||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||||
github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
|
github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
|
||||||
github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
|
github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
|
||||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
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.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
|
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
|
||||||
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
|
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
|
|
||||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
|
|
||||||
github.com/philip-bui/grpc-zerolog v1.0.1 h1:EMacvLRUd2O1K0eWod27ZP5CY1iTNkhBDLSN+Q4JEvA=
|
github.com/philip-bui/grpc-zerolog v1.0.1 h1:EMacvLRUd2O1K0eWod27ZP5CY1iTNkhBDLSN+Q4JEvA=
|
||||||
github.com/philip-bui/grpc-zerolog v1.0.1/go.mod h1:qXbiq/2X4ZUMMshsqlWyTHOcw7ns+GZmlqZZN05ZHcQ=
|
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=
|
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
@@ -401,10 +367,10 @@ 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.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.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
|
||||||
github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
|
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.78 h1:QTWKaIAa4B32GKwqVXtu9m1DUMgWw3VRljMkMevX+b8=
|
||||||
github.com/pterm/pterm v0.12.79/go.mod h1:1v/gzOF1N0FsjbgTHZ1wVycRkKiatFvJSJC4IGaQAAo=
|
github.com/pterm/pterm v0.12.78/go.mod h1:1v/gzOF1N0FsjbgTHZ1wVycRkKiatFvJSJC4IGaQAAo=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
|
github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
@@ -425,8 +391,6 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g
|
|||||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||||
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
|
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
|
||||||
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||||
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
|
|
||||||
github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
|
|
||||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||||
@@ -449,8 +413,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
@@ -458,9 +422,9 @@ 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.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.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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
|
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
|
||||||
@@ -475,22 +439,14 @@ github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29X
|
|||||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
|
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
|
||||||
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk=
|
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk=
|
||||||
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
|
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
|
||||||
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w=
|
github.com/tailscale/setec v0.0.0-20240102233422-ba738f8ab5a0 h1:0bcWsoeSBbY3XWRS1F8yp/g343E5TQMakwy5cxJS+ZU=
|
||||||
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU=
|
github.com/tailscale/setec v0.0.0-20240102233422-ba738f8ab5a0/go.mod h1:/8aqnX9aU8yubwQ2InR5mHi1OlfWQ8ei8Ea2eyLScOY=
|
||||||
github.com/tailscale/setec v0.0.0-20240314234648-9da8e7407257 h1:6WsbDYsikRNmmbfZoRoyIEA9tfl0aspPAE0t7nBj2B4=
|
github.com/tailscale/tailsql v0.0.0-20231216172832-51483e0c711b h1:FzqUT8XFn3OJTzTMteYMZlg3EUQMxoq7oJiaVj4SEBA=
|
||||||
github.com/tailscale/setec v0.0.0-20240314234648-9da8e7407257/go.mod h1:hrq01/0LUDZf4mMkcZ7Ovmy33jvCi4RpESpb9kPxV6E=
|
github.com/tailscale/tailsql v0.0.0-20231216172832-51483e0c711b/go.mod h1:Nkao4BDbQqzxxg78ty4ejq+KgX/0Bxj00DxfxScuJoI=
|
||||||
github.com/tailscale/squibble v0.0.0-20240418235321-9ee0eeb78185 h1:zT+qB+2Ghulj50d5Wq6h6vQYqD2sPdhy4FF6+FHedVE=
|
github.com/tailscale/web-client-prebuilt v0.0.0-20240111230031-5ca22df9e6e7 h1:xAgOVncJuuxkFZ2oXXDKFTH4HDdFYSZRYdA6oMrCewg=
|
||||||
github.com/tailscale/squibble v0.0.0-20240418235321-9ee0eeb78185/go.mod h1:LoIjI6z/6efr9ebISQ5l2vjQmjc8QJrAYZdy3Ec3sVs=
|
github.com/tailscale/web-client-prebuilt v0.0.0-20240111230031-5ca22df9e6e7/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
|
||||||
github.com/tailscale/tailsql v0.0.0-20240418235827-820559f382c1 h1:wmsnxEEuRlgK7Bhdkmm0JGrjjc0JoHZThLLo0WXXbLs=
|
github.com/tailscale/wireguard-go v0.0.0-20231121184858-cc193a0b3272 h1:zwsem4CaamMdC3tFoTpzrsUSMDPV0K6rhnQdF7kXekQ=
|
||||||
github.com/tailscale/tailsql v0.0.0-20240418235827-820559f382c1/go.mod h1:XN193fbz9RR/5stlWPMMIZR+TTa1BUkDJm5Azwzxwgw=
|
github.com/tailscale/wireguard-go v0.0.0-20231121184858-cc193a0b3272/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
|
||||||
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-20240429185444-03c5a0ccf754 h1:iazWjqVHE6CbNam7WXRhi33Qad5o7a8LVYgVoILpZdI=
|
|
||||||
github.com/tailscale/wireguard-go v0.0.0-20240429185444-03c5a0ccf754/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
|
|
||||||
github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9 h1:81P7rjnikHKTJ75EkjppvbwUfKHDHYk6LJpO5PZy8pA=
|
|
||||||
github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
|
|
||||||
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
||||||
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
|
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
|
||||||
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
|
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
|
||||||
@@ -499,8 +455,8 @@ github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e h1:IWllFTiDjjLIf2
|
|||||||
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e/go.mod h1:d7u6HkTYKSv5m6MCKkOQlHwaShTMl3HjqSGW3XtVhXM=
|
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e/go.mod h1:d7u6HkTYKSv5m6MCKkOQlHwaShTMl3HjqSGW3XtVhXM=
|
||||||
github.com/tink-crypto/tink-go/v2 v2.1.0 h1:QXFBguwMwTIaU17EgZpEJWsUSc60b1BAGTzBIoMdmok=
|
github.com/tink-crypto/tink-go/v2 v2.1.0 h1:QXFBguwMwTIaU17EgZpEJWsUSc60b1BAGTzBIoMdmok=
|
||||||
github.com/tink-crypto/tink-go/v2 v2.1.0/go.mod h1:y1TnYFt1i2eZVfx4OGc+C+EMp4CoKWAw2VSEuoicHHI=
|
github.com/tink-crypto/tink-go/v2 v2.1.0/go.mod h1:y1TnYFt1i2eZVfx4OGc+C+EMp4CoKWAw2VSEuoicHHI=
|
||||||
github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs=
|
github.com/u-root/u-root v0.11.0 h1:6gCZLOeRyevw7gbTwMj3fKxnr9+yHFlgF3N7udUVNO8=
|
||||||
github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI=
|
github.com/u-root/u-root v0.11.0/go.mod h1:DBkDtiZyONk9hzVEdB/PWI9B4TxDkElWlVTHseglrZY=
|
||||||
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw=
|
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw=
|
||||||
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
||||||
@@ -534,19 +490,20 @@ go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wus
|
|||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
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.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.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||||
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a h1:8qmSSA8Gz/1kTrCe0nqR0R3Gb/NDhykzWw2q2mWZydM=
|
golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9 h1:j3D9DvWRpUfIyFfDPws7LoIZ2MAI1OJHdQXtTnYtN+k=
|
||||||
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
|
||||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
@@ -555,8 +512,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.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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -566,14 +523,14 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
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.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.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
|
||||||
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -582,8 +539,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-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.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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -594,13 +551,14 @@ golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -612,29 +570,26 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
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.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
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.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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
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=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -648,8 +603,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.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.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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -660,22 +615,28 @@ golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus
|
|||||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||||
|
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
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-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240515191416-fc5f0ca64291 h1:4HZJ3Xv1cmrJ+0aFo304Zn79ur1HMxptAE7aCPNLSqc=
|
google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 h1:g/4bk7P6TPMkAUbUhquq98xey1slwvuVJPosdBqYJlU=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g=
|
google.golang.org/genproto v0.0.0-20240205150955-31a09d347014/go.mod h1:xEgQu1e4stdSSsxPDK8Azkrk/ECl5HvdPf6nbZrTS5M=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 h1:x9PwdEgd11LgK+orcck69WVRo7DezSO4VUMPI4xpc8A=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 h1:FSL3lRCkhaPFxqi0s9o+V4UI2WTzAVOvkgbd4kVV4Wg=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014/go.mod h1:SaPjaZGWb0lPqs6Ittu0spdfrOArqji4ZdeP5IC/9N4=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
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.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
|
||||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
||||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||||
|
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -683,6 +644,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
|||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
@@ -692,32 +655,40 @@ 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
|
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
|
||||||
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
|
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
|
||||||
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||||
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||||
gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3 h1:/8/t5pz/mgdRXhYOIeqqYhFAQLE4DDGegc0Y4ZjyFJM=
|
gvisor.dev/gvisor v0.0.0-20230928000133-4fe30062272c h1:bYb98Ra11fJ8F2xFbZx0zg2VQ28lYqC1JxfaaF53xqY=
|
||||||
gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0=
|
gvisor.dev/gvisor v0.0.0-20230928000133-4fe30062272c/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs=
|
honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8=
|
||||||
honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0=
|
honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0=
|
||||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||||
modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
|
inet.af/peercred v0.0.0-20210906144145-0893ea02156a h1:qdkS8Q5/i10xU2ArJMKYhVa1DORzBfYS/qA2UK2jheg=
|
||||||
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
inet.af/peercred v0.0.0-20210906144145-0893ea02156a/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU=
|
||||||
modernc.org/ccgo/v4 v4.17.7 h1:+MG+Np7uYtsuPvtoH3KtZ1+pqNiJAOqqqVIxggE1iIo=
|
inet.af/wf v0.0.0-20221017222439-36129f591884 h1:zg9snq3Cpy50lWuVqDYM7AIRVTtU50y5WXETMFohW/Q=
|
||||||
modernc.org/ccgo/v4 v4.17.7/go.mod h1:x87xuLLXuJv3Nn5ULTUqJn/HsTMMMiT1Eavo6rz1NiY=
|
inet.af/wf v0.0.0-20221017222439-36129f591884/go.mod h1:bSAQ38BYbY68uwpasXOTZo22dKGy9SNvI6PZFeKomZE=
|
||||||
|
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||||
|
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||||
|
modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q=
|
||||||
|
modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
|
||||||
|
modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk=
|
||||||
|
modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||||
|
modernc.org/ccgo/v3 v3.17.0 h1:o3OmOqx4/OFnl4Vm3G8Bgmqxnvxnh0nbxeT5p/dWChA=
|
||||||
|
modernc.org/ccgo/v3 v3.17.0/go.mod h1:Sg3fwVpmLvCUTaqEUjiBDAvshIaKDB0RXaf+zgqFu8I=
|
||||||
|
modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA=
|
||||||
|
modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI=
|
||||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg=
|
||||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo=
|
||||||
modernc.org/libc v1.50.6 h1:72NPEFMyKP01RJrKXS2eLXv35UklKqlJZ1b9P7gSo6I=
|
|
||||||
modernc.org/libc v1.50.6/go.mod h1:8lr2m1THY5Z3ikGyUc3JhLEQg1oaIBz/AQixw8/eksQ=
|
|
||||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||||
@@ -726,15 +697,15 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
|||||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||||
modernc.org/sqlite v1.29.9 h1:9RhNMklxJs+1596GNuAX+O/6040bvOwacTxuFcRuQow=
|
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
|
||||||
modernc.org/sqlite v1.29.9/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA=
|
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
|
||||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q=
|
nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q=
|
||||||
nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
||||||
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
software.sslmate.com/src/go-pkcs12 v0.2.1 h1:tbT1jjaeFOF230tzOIRJ6U5S1jNqpsSyNjzDd58H3J8=
|
||||||
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
software.sslmate.com/src/go-pkcs12 v0.2.1/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||||
tailscale.com v1.66.3 h1:jpWat+hiobTtCosSV/c8D6S/ubgROf/S59MaIBdM9pY=
|
tailscale.com v1.58.2 h1:5trkhh/fpUn7f6TUcGUQYJ0GokdNNfNrjh9ONJhoc5A=
|
||||||
tailscale.com v1.66.3/go.mod h1:99BIV4U3UPw36Sva04xK2ZsEpVRUkY9jCdEDSAhaNGM=
|
tailscale.com v1.58.2/go.mod h1:faWR8XaXemnSKCDjHC7SAQzaagkUjA5x4jlLWiwxtuk=
|
||||||
|
161
hscontrol/app.go
161
hscontrol/app.go
@@ -19,7 +19,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||||
grpcRuntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
grpcRuntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||||
@@ -71,7 +70,7 @@ var (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
AuthPrefix = "Bearer "
|
AuthPrefix = "Bearer "
|
||||||
updateInterval = 5 * time.Second
|
updateInterval = 5000
|
||||||
privateKeyFileMode = 0o600
|
privateKeyFileMode = 0o600
|
||||||
headscaleDirPerm = 0o700
|
headscaleDirPerm = 0o700
|
||||||
|
|
||||||
@@ -105,15 +104,16 @@ type Headscale struct {
|
|||||||
registrationCache *cache.Cache
|
registrationCache *cache.Cache
|
||||||
|
|
||||||
pollNetMapStreamWG sync.WaitGroup
|
pollNetMapStreamWG sync.WaitGroup
|
||||||
|
|
||||||
|
mapSessions map[types.NodeID]*mapSession
|
||||||
|
mapSessionMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
profilingEnabled = envknob.Bool("HEADSCALE_DEBUG_PROFILING_ENABLED")
|
profilingEnabled = envknob.Bool("HEADSCALE_PROFILING_ENABLED")
|
||||||
profilingPath = envknob.String("HEADSCALE_DEBUG_PROFILING_PATH")
|
|
||||||
tailsqlEnabled = envknob.Bool("HEADSCALE_DEBUG_TAILSQL_ENABLED")
|
tailsqlEnabled = envknob.Bool("HEADSCALE_DEBUG_TAILSQL_ENABLED")
|
||||||
tailsqlStateDir = envknob.String("HEADSCALE_DEBUG_TAILSQL_STATE_DIR")
|
tailsqlStateDir = envknob.String("HEADSCALE_DEBUG_TAILSQL_STATE_DIR")
|
||||||
tailsqlTSKey = envknob.String("TS_AUTHKEY")
|
tailsqlTSKey = envknob.String("TS_AUTHKEY")
|
||||||
dumpConfig = envknob.Bool("HEADSCALE_DEBUG_DUMP_CONFIG")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewHeadscale(cfg *types.Config) (*Headscale, error) {
|
func NewHeadscale(cfg *types.Config) (*Headscale, error) {
|
||||||
@@ -137,7 +137,8 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) {
|
|||||||
noisePrivateKey: noisePrivateKey,
|
noisePrivateKey: noisePrivateKey,
|
||||||
registrationCache: registrationCache,
|
registrationCache: registrationCache,
|
||||||
pollNetMapStreamWG: sync.WaitGroup{},
|
pollNetMapStreamWG: sync.WaitGroup{},
|
||||||
nodeNotifier: notifier.NewNotifier(cfg),
|
nodeNotifier: notifier.NewNotifier(),
|
||||||
|
mapSessions: make(map[types.NodeID]*mapSession),
|
||||||
}
|
}
|
||||||
|
|
||||||
app.db, err = db.NewHeadscaleDatabase(
|
app.db, err = db.NewHeadscaleDatabase(
|
||||||
@@ -218,75 +219,64 @@ func (h *Headscale) redirect(w http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
// deleteExpireEphemeralNodes deletes ephemeral node records that have not been
|
// deleteExpireEphemeralNodes deletes ephemeral node records that have not been
|
||||||
// seen for longer than h.cfg.EphemeralNodeInactivityTimeout.
|
// seen for longer than h.cfg.EphemeralNodeInactivityTimeout.
|
||||||
func (h *Headscale) deleteExpireEphemeralNodes(ctx context.Context, every time.Duration) {
|
func (h *Headscale) deleteExpireEphemeralNodes(milliSeconds int64) {
|
||||||
ticker := time.NewTicker(every)
|
ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
|
||||||
|
|
||||||
for {
|
for range ticker.C {
|
||||||
select {
|
var removed []types.NodeID
|
||||||
case <-ctx.Done():
|
var changed []types.NodeID
|
||||||
ticker.Stop()
|
if err := h.db.Write(func(tx *gorm.DB) error {
|
||||||
return
|
removed, changed = db.DeleteExpiredEphemeralNodes(tx, h.cfg.EphemeralNodeInactivityTimeout)
|
||||||
case <-ticker.C:
|
|
||||||
var removed []types.NodeID
|
|
||||||
var changed []types.NodeID
|
|
||||||
if err := h.db.Write(func(tx *gorm.DB) error {
|
|
||||||
removed, changed = db.DeleteExpiredEphemeralNodes(tx, h.cfg.EphemeralNodeInactivityTimeout)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Error().Err(err).Msg("database error while expiring ephemeral nodes")
|
log.Error().Err(err).Msg("database error while expiring ephemeral nodes")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if removed != nil {
|
if removed != nil {
|
||||||
ctx := types.NotifyCtx(context.Background(), "expire-ephemeral", "na")
|
ctx := types.NotifyCtx(context.Background(), "expire-ephemeral", "na")
|
||||||
h.nodeNotifier.NotifyAll(ctx, types.StateUpdate{
|
h.nodeNotifier.NotifyAll(ctx, types.StateUpdate{
|
||||||
Type: types.StatePeerRemoved,
|
Type: types.StatePeerRemoved,
|
||||||
Removed: removed,
|
Removed: removed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if changed != nil {
|
if changed != nil {
|
||||||
ctx := types.NotifyCtx(context.Background(), "expire-ephemeral", "na")
|
ctx := types.NotifyCtx(context.Background(), "expire-ephemeral", "na")
|
||||||
h.nodeNotifier.NotifyAll(ctx, types.StateUpdate{
|
h.nodeNotifier.NotifyAll(ctx, types.StateUpdate{
|
||||||
Type: types.StatePeerChanged,
|
Type: types.StatePeerChanged,
|
||||||
ChangeNodes: changed,
|
ChangeNodes: changed,
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// expireExpiredNodes expires nodes that have an explicit expiry set
|
// expireExpiredMachines expires nodes that have an explicit expiry set
|
||||||
// after that expiry time has passed.
|
// after that expiry time has passed.
|
||||||
func (h *Headscale) expireExpiredNodes(ctx context.Context, every time.Duration) {
|
func (h *Headscale) expireExpiredMachines(intervalMs int64) {
|
||||||
ticker := time.NewTicker(every)
|
interval := time.Duration(intervalMs) * time.Millisecond
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
|
||||||
lastCheck := time.Unix(0, 0)
|
lastCheck := time.Unix(0, 0)
|
||||||
var update types.StateUpdate
|
var update types.StateUpdate
|
||||||
var changed bool
|
var changed bool
|
||||||
|
|
||||||
for {
|
for range ticker.C {
|
||||||
select {
|
if err := h.db.Write(func(tx *gorm.DB) error {
|
||||||
case <-ctx.Done():
|
lastCheck, update, changed = db.ExpireExpiredNodes(tx, lastCheck)
|
||||||
ticker.Stop()
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
if err := h.db.Write(func(tx *gorm.DB) error {
|
|
||||||
lastCheck, update, changed = db.ExpireExpiredNodes(tx, lastCheck)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Error().Err(err).Msg("database error while expiring nodes")
|
log.Error().Err(err).Msg("database error while expiring nodes")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
log.Trace().Interface("nodes", update.ChangePatches).Msgf("expiring nodes")
|
log.Trace().Interface("nodes", update.ChangePatches).Msgf("expiring nodes")
|
||||||
|
|
||||||
ctx := types.NotifyCtx(context.Background(), "expire-expired", "na")
|
ctx := types.NotifyCtx(context.Background(), "expire-expired", "na")
|
||||||
h.nodeNotifier.NotifyAll(ctx, update)
|
h.nodeNotifier.NotifyAll(ctx, update)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,7 +319,7 @@ func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context,
|
|||||||
// Check if the request is coming from the on-server client.
|
// Check if the request is coming from the on-server client.
|
||||||
// This is not secure, but it is to maintain maintainability
|
// This is not secure, but it is to maintain maintainability
|
||||||
// with the "legacy" database-based client
|
// with the "legacy" database-based client
|
||||||
// It is also needed for grpc-gateway to be able to connect to
|
// It is also neede for grpc-gateway to be able to connect to
|
||||||
// the server
|
// the server
|
||||||
client, _ := peer.FromContext(ctx)
|
client, _ := peer.FromContext(ctx)
|
||||||
|
|
||||||
@@ -501,14 +491,14 @@ func (h *Headscale) createRouter(grpcMux *grpcRuntime.ServeMux) *mux.Router {
|
|||||||
|
|
||||||
// Serve launches the HTTP and gRPC server service Headscale and the API.
|
// Serve launches the HTTP and gRPC server service Headscale and the API.
|
||||||
func (h *Headscale) Serve() error {
|
func (h *Headscale) Serve() error {
|
||||||
if profilingEnabled {
|
if _, enableProfile := os.LookupEnv("HEADSCALE_PROFILING_ENABLED"); enableProfile {
|
||||||
if profilingPath != "" {
|
if profilePath, ok := os.LookupEnv("HEADSCALE_PROFILING_PATH"); ok {
|
||||||
err := os.MkdirAll(profilingPath, os.ModePerm)
|
err := os.MkdirAll(profilePath, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("failed to create profiling directory")
|
log.Fatal().Err(err).Msg("failed to create profiling directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer profile.Start(profile.ProfilePath(profilingPath)).Stop()
|
defer profile.Start(profile.ProfilePath(profilePath)).Stop()
|
||||||
} else {
|
} else {
|
||||||
defer profile.Start().Stop()
|
defer profile.Start().Stop()
|
||||||
}
|
}
|
||||||
@@ -516,10 +506,6 @@ func (h *Headscale) Serve() error {
|
|||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if dumpConfig {
|
|
||||||
spew.Dump(h.cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch an initial DERP Map before we start serving
|
// Fetch an initial DERP Map before we start serving
|
||||||
h.DERPMap = derp.GetDERPMap(h.cfg.DERP)
|
h.DERPMap = derp.GetDERPMap(h.cfg.DERP)
|
||||||
h.mapper = mapper.NewMapper(h.db, h.cfg, h.DERPMap, h.nodeNotifier)
|
h.mapper = mapper.NewMapper(h.db, h.cfg, h.DERPMap, h.nodeNotifier)
|
||||||
@@ -552,13 +538,10 @@ func (h *Headscale) Serve() error {
|
|||||||
return errEmptyInitialDERPMap
|
return errEmptyInitialDERPMap
|
||||||
}
|
}
|
||||||
|
|
||||||
expireEphemeralCtx, expireEphemeralCancel := context.WithCancel(context.Background())
|
// TODO(kradalby): These should have cancel channels and be cleaned
|
||||||
defer expireEphemeralCancel()
|
// up on shutdown.
|
||||||
go h.deleteExpireEphemeralNodes(expireEphemeralCtx, updateInterval)
|
go h.deleteExpireEphemeralNodes(updateInterval)
|
||||||
|
go h.expireExpiredMachines(updateInterval)
|
||||||
expireNodeCtx, expireNodeCancel := context.WithCancel(context.Background())
|
|
||||||
defer expireNodeCancel()
|
|
||||||
go h.expireExpiredNodes(expireNodeCtx, updateInterval)
|
|
||||||
|
|
||||||
if zl.GlobalLevel() == zl.TraceLevel {
|
if zl.GlobalLevel() == zl.TraceLevel {
|
||||||
zerolog.RespLog = true
|
zerolog.RespLog = true
|
||||||
@@ -732,6 +715,19 @@ func (h *Headscale) Serve() error {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte(h.nodeNotifier.String()))
|
w.Write([]byte(h.nodeNotifier.String()))
|
||||||
})
|
})
|
||||||
|
debugMux.HandleFunc("/debug/mapresp", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.mapSessionMu.Lock()
|
||||||
|
defer h.mapSessionMu.Unlock()
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("mapresponders:\n")
|
||||||
|
for k, v := range h.mapSessions {
|
||||||
|
fmt.Fprintf(&b, "\t%d: %p\n", k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(b.String()))
|
||||||
|
})
|
||||||
debugMux.Handle("/metrics", promhttp.Handler())
|
debugMux.Handle("/metrics", promhttp.Handler())
|
||||||
|
|
||||||
debugHTTPServer := &http.Server{
|
debugHTTPServer := &http.Server{
|
||||||
@@ -804,15 +800,10 @@ func (h *Headscale) Serve() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
trace := log.Trace().Msgf
|
|
||||||
log.Info().
|
log.Info().
|
||||||
Str("signal", sig.String()).
|
Str("signal", sig.String()).
|
||||||
Msg("Received signal to stop, shutting down gracefully")
|
Msg("Received signal to stop, shutting down gracefully")
|
||||||
|
|
||||||
expireNodeCancel()
|
|
||||||
expireEphemeralCancel()
|
|
||||||
|
|
||||||
trace("waiting for netmap stream to close")
|
|
||||||
h.pollNetMapStreamWG.Wait()
|
h.pollNetMapStreamWG.Wait()
|
||||||
|
|
||||||
// Gracefully shut down servers
|
// Gracefully shut down servers
|
||||||
@@ -820,44 +811,32 @@ func (h *Headscale) Serve() error {
|
|||||||
context.Background(),
|
context.Background(),
|
||||||
types.HTTPShutdownTimeout,
|
types.HTTPShutdownTimeout,
|
||||||
)
|
)
|
||||||
trace("shutting down debug http server")
|
|
||||||
if err := debugHTTPServer.Shutdown(ctx); err != nil {
|
if err := debugHTTPServer.Shutdown(ctx); err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to shutdown prometheus http")
|
log.Error().Err(err).Msg("Failed to shutdown prometheus http")
|
||||||
}
|
}
|
||||||
trace("shutting down main http server")
|
|
||||||
if err := httpServer.Shutdown(ctx); err != nil {
|
if err := httpServer.Shutdown(ctx); err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to shutdown http")
|
log.Error().Err(err).Msg("Failed to shutdown http")
|
||||||
}
|
}
|
||||||
|
|
||||||
trace("shutting down grpc server (socket)")
|
|
||||||
grpcSocket.GracefulStop()
|
grpcSocket.GracefulStop()
|
||||||
|
|
||||||
if grpcServer != nil {
|
if grpcServer != nil {
|
||||||
trace("shutting down grpc server (external)")
|
|
||||||
grpcServer.GracefulStop()
|
grpcServer.GracefulStop()
|
||||||
grpcListener.Close()
|
grpcListener.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
if tailsqlContext != nil {
|
if tailsqlContext != nil {
|
||||||
trace("shutting down tailsql")
|
|
||||||
tailsqlContext.Done()
|
tailsqlContext.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
trace("closing node notifier")
|
|
||||||
h.nodeNotifier.Close()
|
|
||||||
|
|
||||||
// Close network listeners
|
// Close network listeners
|
||||||
trace("closing network listeners")
|
|
||||||
debugHTTPListener.Close()
|
debugHTTPListener.Close()
|
||||||
httpListener.Close()
|
httpListener.Close()
|
||||||
grpcGatewayConn.Close()
|
grpcGatewayConn.Close()
|
||||||
|
|
||||||
// Stop listening (and unlink the socket if unix type):
|
// Stop listening (and unlink the socket if unix type):
|
||||||
trace("closing socket listener")
|
|
||||||
socketListener.Close()
|
socketListener.Close()
|
||||||
|
|
||||||
// Close db connections
|
// Close db connections
|
||||||
trace("closing database connection")
|
|
||||||
err = h.db.Close()
|
err = h.db.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to close db")
|
log.Error().Err(err).Msg("Failed to close db")
|
||||||
|
@@ -62,18 +62,18 @@ func logAuthFunc(
|
|||||||
func (h *Headscale) handleRegister(
|
func (h *Headscale) handleRegister(
|
||||||
writer http.ResponseWriter,
|
writer http.ResponseWriter,
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
regReq tailcfg.RegisterRequest,
|
registerRequest tailcfg.RegisterRequest,
|
||||||
machineKey key.MachinePublic,
|
machineKey key.MachinePublic,
|
||||||
) {
|
) {
|
||||||
logInfo, logTrace, logErr := logAuthFunc(regReq, machineKey)
|
logInfo, logTrace, logErr := logAuthFunc(registerRequest, machineKey)
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
logTrace("handleRegister called, looking up machine in DB")
|
logTrace("handleRegister called, looking up machine in DB")
|
||||||
node, err := h.db.GetNodeByAnyKey(machineKey, regReq.NodeKey, regReq.OldNodeKey)
|
node, err := h.db.GetNodeByAnyKey(machineKey, registerRequest.NodeKey, registerRequest.OldNodeKey)
|
||||||
logTrace("handleRegister database lookup has returned")
|
logTrace("handleRegister database lookup has returned")
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
// If the node has AuthKey set, handle registration via PreAuthKeys
|
// If the node has AuthKey set, handle registration via PreAuthKeys
|
||||||
if regReq.Auth != nil && regReq.Auth.AuthKey != "" {
|
if registerRequest.Auth.AuthKey != "" {
|
||||||
h.handleAuthKey(writer, regReq, machineKey)
|
h.handleAuthKey(writer, registerRequest, machineKey)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ func (h *Headscale) handleRegister(
|
|||||||
// This is not implemented yet, as it is no strictly required. The only side-effect
|
// This is not implemented yet, as it is no strictly required. The only side-effect
|
||||||
// is that the client will hammer headscale with requests until it gets a
|
// is that the client will hammer headscale with requests until it gets a
|
||||||
// successful RegisterResponse.
|
// successful RegisterResponse.
|
||||||
if regReq.Followup != "" {
|
if registerRequest.Followup != "" {
|
||||||
logTrace("register request is a followup")
|
logTrace("register request is a followup")
|
||||||
if _, ok := h.registrationCache.Get(machineKey.String()); ok {
|
if _, ok := h.registrationCache.Get(machineKey.String()); ok {
|
||||||
logTrace("Node is waiting for interactive login")
|
logTrace("Node is waiting for interactive login")
|
||||||
@@ -95,7 +95,7 @@ func (h *Headscale) handleRegister(
|
|||||||
case <-req.Context().Done():
|
case <-req.Context().Done():
|
||||||
return
|
return
|
||||||
case <-time.After(registrationHoldoff):
|
case <-time.After(registrationHoldoff):
|
||||||
h.handleNewNode(writer, regReq, machineKey)
|
h.handleNewNode(writer, registerRequest, machineKey)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ func (h *Headscale) handleRegister(
|
|||||||
|
|
||||||
givenName, err := h.db.GenerateGivenName(
|
givenName, err := h.db.GenerateGivenName(
|
||||||
machineKey,
|
machineKey,
|
||||||
regReq.Hostinfo.Hostname,
|
registerRequest.Hostinfo.Hostname,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logErr(err, "Failed to generate given name for node")
|
logErr(err, "Failed to generate given name for node")
|
||||||
@@ -120,16 +120,16 @@ func (h *Headscale) handleRegister(
|
|||||||
// happens
|
// happens
|
||||||
newNode := types.Node{
|
newNode := types.Node{
|
||||||
MachineKey: machineKey,
|
MachineKey: machineKey,
|
||||||
Hostname: regReq.Hostinfo.Hostname,
|
Hostname: registerRequest.Hostinfo.Hostname,
|
||||||
GivenName: givenName,
|
GivenName: givenName,
|
||||||
NodeKey: regReq.NodeKey,
|
NodeKey: registerRequest.NodeKey,
|
||||||
LastSeen: &now,
|
LastSeen: &now,
|
||||||
Expiry: &time.Time{},
|
Expiry: &time.Time{},
|
||||||
}
|
}
|
||||||
|
|
||||||
if !regReq.Expiry.IsZero() {
|
if !registerRequest.Expiry.IsZero() {
|
||||||
logTrace("Non-zero expiry time requested")
|
logTrace("Non-zero expiry time requested")
|
||||||
newNode.Expiry = ®Req.Expiry
|
newNode.Expiry = ®isterRequest.Expiry
|
||||||
}
|
}
|
||||||
|
|
||||||
h.registrationCache.Set(
|
h.registrationCache.Set(
|
||||||
@@ -138,7 +138,7 @@ func (h *Headscale) handleRegister(
|
|||||||
registerCacheExpiration,
|
registerCacheExpiration,
|
||||||
)
|
)
|
||||||
|
|
||||||
h.handleNewNode(writer, regReq, machineKey)
|
h.handleNewNode(writer, registerRequest, machineKey)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -169,11 +169,11 @@ func (h *Headscale) handleRegister(
|
|||||||
// - Trying to log out (sending a expiry in the past)
|
// - Trying to log out (sending a expiry in the past)
|
||||||
// - A valid, registered node, looking for /map
|
// - A valid, registered node, looking for /map
|
||||||
// - Expired node wanting to reauthenticate
|
// - Expired node wanting to reauthenticate
|
||||||
if node.NodeKey.String() == regReq.NodeKey.String() {
|
if node.NodeKey.String() == registerRequest.NodeKey.String() {
|
||||||
// The client sends an Expiry in the past if the client is requesting to expire the key (aka logout)
|
// The client sends an Expiry in the past if the client is requesting to expire the key (aka logout)
|
||||||
// https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L648
|
// https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L648
|
||||||
if !regReq.Expiry.IsZero() &&
|
if !registerRequest.Expiry.IsZero() &&
|
||||||
regReq.Expiry.UTC().Before(now) {
|
registerRequest.Expiry.UTC().Before(now) {
|
||||||
h.handleNodeLogOut(writer, *node, machineKey)
|
h.handleNodeLogOut(writer, *node, machineKey)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -189,11 +189,11 @@ func (h *Headscale) handleRegister(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The NodeKey we have matches OldNodeKey, which means this is a refresh after a key expiration
|
// The NodeKey we have matches OldNodeKey, which means this is a refresh after a key expiration
|
||||||
if node.NodeKey.String() == regReq.OldNodeKey.String() &&
|
if node.NodeKey.String() == registerRequest.OldNodeKey.String() &&
|
||||||
!node.IsExpired() {
|
!node.IsExpired() {
|
||||||
h.handleNodeKeyRefresh(
|
h.handleNodeKeyRefresh(
|
||||||
writer,
|
writer,
|
||||||
regReq,
|
registerRequest,
|
||||||
*node,
|
*node,
|
||||||
machineKey,
|
machineKey,
|
||||||
)
|
)
|
||||||
@@ -202,11 +202,11 @@ func (h *Headscale) handleRegister(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// When logged out and reauthenticating with OIDC, the OldNodeKey is not passed, but the NodeKey has changed
|
// When logged out and reauthenticating with OIDC, the OldNodeKey is not passed, but the NodeKey has changed
|
||||||
if node.NodeKey.String() != regReq.NodeKey.String() &&
|
if node.NodeKey.String() != registerRequest.NodeKey.String() &&
|
||||||
regReq.OldNodeKey.IsZero() && !node.IsExpired() {
|
registerRequest.OldNodeKey.IsZero() && !node.IsExpired() {
|
||||||
h.handleNodeKeyRefresh(
|
h.handleNodeKeyRefresh(
|
||||||
writer,
|
writer,
|
||||||
regReq,
|
registerRequest,
|
||||||
*node,
|
*node,
|
||||||
machineKey,
|
machineKey,
|
||||||
)
|
)
|
||||||
@@ -214,7 +214,7 @@ func (h *Headscale) handleRegister(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if regReq.Followup != "" {
|
if registerRequest.Followup != "" {
|
||||||
select {
|
select {
|
||||||
case <-req.Context().Done():
|
case <-req.Context().Done():
|
||||||
return
|
return
|
||||||
@@ -223,7 +223,7 @@ func (h *Headscale) handleRegister(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The node has expired or it is logged out
|
// The node has expired or it is logged out
|
||||||
h.handleNodeExpiredOrLoggedOut(writer, regReq, *node, machineKey)
|
h.handleNodeExpiredOrLoggedOut(writer, registerRequest, *node, machineKey)
|
||||||
|
|
||||||
// TODO(juan): RegisterRequest includes an Expiry time, that we could optionally use
|
// TODO(juan): RegisterRequest includes an Expiry time, that we could optionally use
|
||||||
node.Expiry = &time.Time{}
|
node.Expiry = &time.Time{}
|
||||||
@@ -232,7 +232,7 @@ func (h *Headscale) handleRegister(
|
|||||||
// we need to make sure the NodeKey matches the one in the request
|
// we need to make sure the NodeKey matches the one in the request
|
||||||
// TODO(juan): What happens when using fast user switching between two
|
// TODO(juan): What happens when using fast user switching between two
|
||||||
// headscale-managed tailnets?
|
// headscale-managed tailnets?
|
||||||
node.NodeKey = regReq.NodeKey
|
node.NodeKey = registerRequest.NodeKey
|
||||||
h.registrationCache.Set(
|
h.registrationCache.Set(
|
||||||
machineKey.String(),
|
machineKey.String(),
|
||||||
*node,
|
*node,
|
||||||
@@ -314,21 +314,14 @@ func (h *Headscale) handleAuthKey(
|
|||||||
Msg("node was already registered before, refreshing with new auth key")
|
Msg("node was already registered before, refreshing with new auth key")
|
||||||
|
|
||||||
node.NodeKey = nodeKey
|
node.NodeKey = nodeKey
|
||||||
pakID := uint(pak.ID)
|
node.AuthKeyID = uint(pak.ID)
|
||||||
if pakID != 0 {
|
err := h.db.NodeSetExpiry(node.ID, registerRequest.Expiry)
|
||||||
node.AuthKeyID = &pakID
|
|
||||||
}
|
|
||||||
|
|
||||||
node.Expiry = ®isterRequest.Expiry
|
|
||||||
node.User = pak.User
|
|
||||||
node.UserID = pak.UserID
|
|
||||||
err := h.db.DB.Save(node).Error
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Caller().
|
Caller().
|
||||||
Str("node", node.Hostname).
|
Str("node", node.Hostname).
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("failed to save node after logging in with auth key")
|
Msg("Failed to refresh node")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -351,7 +344,7 @@ func (h *Headscale) handleAuthKey(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := types.NotifyCtx(context.Background(), "handle-authkey", "na")
|
ctx := types.NotifyCtx(context.Background(), "handle-authkey", "na")
|
||||||
h.nodeNotifier.NotifyAll(ctx, types.StateUpdate{Type: types.StatePeerChanged, ChangeNodes: []types.NodeID{node.ID}})
|
h.nodeNotifier.NotifyWithIgnore(ctx, types.StateUpdateExpire(node.ID, registerRequest.Expiry), node.ID)
|
||||||
} else {
|
} else {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
|
|
||||||
@@ -377,6 +370,7 @@ func (h *Headscale) handleAuthKey(
|
|||||||
Expiry: ®isterRequest.Expiry,
|
Expiry: ®isterRequest.Expiry,
|
||||||
NodeKey: nodeKey,
|
NodeKey: nodeKey,
|
||||||
LastSeen: &now,
|
LastSeen: &now,
|
||||||
|
AuthKeyID: uint(pak.ID),
|
||||||
ForcedTags: pak.Proto().GetAclTags(),
|
ForcedTags: pak.Proto().GetAclTags(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,10 +386,6 @@ func (h *Headscale) handleAuthKey(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pakID := uint(pak.ID)
|
|
||||||
if pakID != 0 {
|
|
||||||
nodeToRegister.AuthKeyID = &pakID
|
|
||||||
}
|
|
||||||
node, err = h.db.RegisterNode(
|
node, err = h.db.RegisterNode(
|
||||||
nodeToRegister,
|
nodeToRegister,
|
||||||
ipv4, ipv6,
|
ipv4, ipv6,
|
||||||
@@ -689,14 +679,14 @@ func (h *Headscale) handleNodeKeyRefresh(
|
|||||||
|
|
||||||
func (h *Headscale) handleNodeExpiredOrLoggedOut(
|
func (h *Headscale) handleNodeExpiredOrLoggedOut(
|
||||||
writer http.ResponseWriter,
|
writer http.ResponseWriter,
|
||||||
regReq tailcfg.RegisterRequest,
|
registerRequest tailcfg.RegisterRequest,
|
||||||
node types.Node,
|
node types.Node,
|
||||||
machineKey key.MachinePublic,
|
machineKey key.MachinePublic,
|
||||||
) {
|
) {
|
||||||
resp := tailcfg.RegisterResponse{}
|
resp := tailcfg.RegisterResponse{}
|
||||||
|
|
||||||
if regReq.Auth != nil && regReq.Auth.AuthKey != "" {
|
if registerRequest.Auth.AuthKey != "" {
|
||||||
h.handleAuthKey(writer, regReq, machineKey)
|
h.handleAuthKey(writer, registerRequest, machineKey)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -706,8 +696,8 @@ func (h *Headscale) handleNodeExpiredOrLoggedOut(
|
|||||||
Caller().
|
Caller().
|
||||||
Str("node", node.Hostname).
|
Str("node", node.Hostname).
|
||||||
Str("machine_key", machineKey.ShortString()).
|
Str("machine_key", machineKey.ShortString()).
|
||||||
Str("node_key", regReq.NodeKey.ShortString()).
|
Str("node_key", registerRequest.NodeKey.ShortString()).
|
||||||
Str("node_key_old", regReq.OldNodeKey.ShortString()).
|
Str("node_key_old", registerRequest.OldNodeKey.ShortString()).
|
||||||
Msg("Node registration has expired or logged out. Sending a auth url to register")
|
Msg("Node registration has expired or logged out. Sending a auth url to register")
|
||||||
|
|
||||||
if h.oauth2Config != nil {
|
if h.oauth2Config != nil {
|
||||||
@@ -744,8 +734,8 @@ func (h *Headscale) handleNodeExpiredOrLoggedOut(
|
|||||||
log.Trace().
|
log.Trace().
|
||||||
Caller().
|
Caller().
|
||||||
Str("machine_key", machineKey.ShortString()).
|
Str("machine_key", machineKey.ShortString()).
|
||||||
Str("node_key", regReq.NodeKey.ShortString()).
|
Str("node_key", registerRequest.NodeKey.ShortString()).
|
||||||
Str("node_key_old", regReq.OldNodeKey.ShortString()).
|
Str("node_key_old", registerRequest.OldNodeKey.ShortString()).
|
||||||
Str("node", node.Hostname).
|
Str("node", node.Hostname).
|
||||||
Msg("Node logged out. Sent AuthURL for reauthentication")
|
Msg("Node logged out. Sent AuthURL for reauthentication")
|
||||||
}
|
}
|
||||||
|
@@ -91,8 +91,7 @@ func NewHeadscaleDatabase(
|
|||||||
_ = tx.Migrator().
|
_ = tx.Migrator().
|
||||||
RenameColumn(&types.Node{}, "nickname", "given_name")
|
RenameColumn(&types.Node{}, "nickname", "given_name")
|
||||||
|
|
||||||
dbConn.Model(&types.Node{}).Where("auth_key_id = ?", 0).Update("auth_key_id", nil)
|
// If the Node table has a column for registered,
|
||||||
// If the Node table has a column for registered,
|
|
||||||
// find all occourences of "false" and drop them. Then
|
// find all occourences of "false" and drop them. Then
|
||||||
// remove the column.
|
// remove the column.
|
||||||
if tx.Migrator().HasColumn(&types.Node{}, "registered") {
|
if tx.Migrator().HasColumn(&types.Node{}, "registered") {
|
||||||
@@ -442,7 +441,8 @@ func openDB(cfg types.DatabaseConfig) (*gorm.DB, error) {
|
|||||||
db, err := gorm.Open(
|
db, err := gorm.Open(
|
||||||
sqlite.Open(cfg.Sqlite.Path+"?_synchronous=1&_journal_mode=WAL"),
|
sqlite.Open(cfg.Sqlite.Path+"?_synchronous=1&_journal_mode=WAL"),
|
||||||
&gorm.Config{
|
&gorm.Config{
|
||||||
Logger: dbLogger,
|
DisableForeignKeyConstraintWhenMigrating: true,
|
||||||
|
Logger: dbLogger,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -488,7 +488,8 @@ func openDB(cfg types.DatabaseConfig) (*gorm.DB, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db, err := gorm.Open(postgres.Open(dbString), &gorm.Config{
|
db, err := gorm.Open(postgres.Open(dbString), &gorm.Config{
|
||||||
Logger: dbLogger,
|
DisableForeignKeyConstraintWhenMigrating: true,
|
||||||
|
Logger: dbLogger,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@@ -87,11 +87,8 @@ func TestIPAllocatorSequential(t *testing.T) {
|
|||||||
name: "simple-with-db",
|
name: "simple-with-db",
|
||||||
dbFunc: func() *HSDatabase {
|
dbFunc: func() *HSDatabase {
|
||||||
db := dbForTest(t, "simple-with-db")
|
db := dbForTest(t, "simple-with-db")
|
||||||
user := types.User{Name: ""}
|
|
||||||
db.DB.Save(&user)
|
|
||||||
|
|
||||||
db.DB.Save(&types.Node{
|
db.DB.Save(&types.Node{
|
||||||
User: user,
|
|
||||||
IPv4: nap("100.64.0.1"),
|
IPv4: nap("100.64.0.1"),
|
||||||
IPv6: nap("fd7a:115c:a1e0::1"),
|
IPv6: nap("fd7a:115c:a1e0::1"),
|
||||||
})
|
})
|
||||||
@@ -115,11 +112,8 @@ func TestIPAllocatorSequential(t *testing.T) {
|
|||||||
name: "before-after-free-middle-in-db",
|
name: "before-after-free-middle-in-db",
|
||||||
dbFunc: func() *HSDatabase {
|
dbFunc: func() *HSDatabase {
|
||||||
db := dbForTest(t, "before-after-free-middle-in-db")
|
db := dbForTest(t, "before-after-free-middle-in-db")
|
||||||
user := types.User{Name: ""}
|
|
||||||
db.DB.Save(&user)
|
|
||||||
|
|
||||||
db.DB.Save(&types.Node{
|
db.DB.Save(&types.Node{
|
||||||
User: user,
|
|
||||||
IPv4: nap("100.64.0.2"),
|
IPv4: nap("100.64.0.2"),
|
||||||
IPv6: nap("fd7a:115c:a1e0::2"),
|
IPv6: nap("fd7a:115c:a1e0::2"),
|
||||||
})
|
})
|
||||||
@@ -313,11 +307,8 @@ func TestBackfillIPAddresses(t *testing.T) {
|
|||||||
name: "simple-backfill-ipv6",
|
name: "simple-backfill-ipv6",
|
||||||
dbFunc: func() *HSDatabase {
|
dbFunc: func() *HSDatabase {
|
||||||
db := dbForTest(t, "simple-backfill-ipv6")
|
db := dbForTest(t, "simple-backfill-ipv6")
|
||||||
user := types.User{Name: ""}
|
|
||||||
db.DB.Save(&user)
|
|
||||||
|
|
||||||
db.DB.Save(&types.Node{
|
db.DB.Save(&types.Node{
|
||||||
User: user,
|
|
||||||
IPv4: nap("100.64.0.1"),
|
IPv4: nap("100.64.0.1"),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -346,11 +337,8 @@ func TestBackfillIPAddresses(t *testing.T) {
|
|||||||
name: "simple-backfill-ipv4",
|
name: "simple-backfill-ipv4",
|
||||||
dbFunc: func() *HSDatabase {
|
dbFunc: func() *HSDatabase {
|
||||||
db := dbForTest(t, "simple-backfill-ipv4")
|
db := dbForTest(t, "simple-backfill-ipv4")
|
||||||
user := types.User{Name: ""}
|
|
||||||
db.DB.Save(&user)
|
|
||||||
|
|
||||||
db.DB.Save(&types.Node{
|
db.DB.Save(&types.Node{
|
||||||
User: user,
|
|
||||||
IPv6: nap("fd7a:115c:a1e0::1"),
|
IPv6: nap("fd7a:115c:a1e0::1"),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -379,11 +367,8 @@ func TestBackfillIPAddresses(t *testing.T) {
|
|||||||
name: "simple-backfill-remove-ipv6",
|
name: "simple-backfill-remove-ipv6",
|
||||||
dbFunc: func() *HSDatabase {
|
dbFunc: func() *HSDatabase {
|
||||||
db := dbForTest(t, "simple-backfill-remove-ipv6")
|
db := dbForTest(t, "simple-backfill-remove-ipv6")
|
||||||
user := types.User{Name: ""}
|
|
||||||
db.DB.Save(&user)
|
|
||||||
|
|
||||||
db.DB.Save(&types.Node{
|
db.DB.Save(&types.Node{
|
||||||
User: user,
|
|
||||||
IPv4: nap("100.64.0.1"),
|
IPv4: nap("100.64.0.1"),
|
||||||
IPv6: nap("fd7a:115c:a1e0::1"),
|
IPv6: nap("fd7a:115c:a1e0::1"),
|
||||||
})
|
})
|
||||||
@@ -407,11 +392,8 @@ func TestBackfillIPAddresses(t *testing.T) {
|
|||||||
name: "simple-backfill-remove-ipv4",
|
name: "simple-backfill-remove-ipv4",
|
||||||
dbFunc: func() *HSDatabase {
|
dbFunc: func() *HSDatabase {
|
||||||
db := dbForTest(t, "simple-backfill-remove-ipv4")
|
db := dbForTest(t, "simple-backfill-remove-ipv4")
|
||||||
user := types.User{Name: ""}
|
|
||||||
db.DB.Save(&user)
|
|
||||||
|
|
||||||
db.DB.Save(&types.Node{
|
db.DB.Save(&types.Node{
|
||||||
User: user,
|
|
||||||
IPv4: nap("100.64.0.1"),
|
IPv4: nap("100.64.0.1"),
|
||||||
IPv6: nap("fd7a:115c:a1e0::1"),
|
IPv6: nap("fd7a:115c:a1e0::1"),
|
||||||
})
|
})
|
||||||
@@ -435,23 +417,17 @@ func TestBackfillIPAddresses(t *testing.T) {
|
|||||||
name: "multi-backfill-ipv6",
|
name: "multi-backfill-ipv6",
|
||||||
dbFunc: func() *HSDatabase {
|
dbFunc: func() *HSDatabase {
|
||||||
db := dbForTest(t, "simple-backfill-ipv6")
|
db := dbForTest(t, "simple-backfill-ipv6")
|
||||||
user := types.User{Name: ""}
|
|
||||||
db.DB.Save(&user)
|
|
||||||
|
|
||||||
db.DB.Save(&types.Node{
|
db.DB.Save(&types.Node{
|
||||||
User: user,
|
|
||||||
IPv4: nap("100.64.0.1"),
|
IPv4: nap("100.64.0.1"),
|
||||||
})
|
})
|
||||||
db.DB.Save(&types.Node{
|
db.DB.Save(&types.Node{
|
||||||
User: user,
|
|
||||||
IPv4: nap("100.64.0.2"),
|
IPv4: nap("100.64.0.2"),
|
||||||
})
|
})
|
||||||
db.DB.Save(&types.Node{
|
db.DB.Save(&types.Node{
|
||||||
User: user,
|
|
||||||
IPv4: nap("100.64.0.3"),
|
IPv4: nap("100.64.0.3"),
|
||||||
})
|
})
|
||||||
db.DB.Save(&types.Node{
|
db.DB.Save(&types.Node{
|
||||||
User: user,
|
|
||||||
IPv4: nap("100.64.0.4"),
|
IPv4: nap("100.64.0.4"),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -475,8 +451,6 @@ func TestBackfillIPAddresses(t *testing.T) {
|
|||||||
"MachineKeyDatabaseField",
|
"MachineKeyDatabaseField",
|
||||||
"NodeKeyDatabaseField",
|
"NodeKeyDatabaseField",
|
||||||
"DiscoKeyDatabaseField",
|
"DiscoKeyDatabaseField",
|
||||||
"User",
|
|
||||||
"UserID",
|
|
||||||
"Endpoints",
|
"Endpoints",
|
||||||
"HostinfoDatabaseField",
|
"HostinfoDatabaseField",
|
||||||
"Hostinfo",
|
"Hostinfo",
|
||||||
|
@@ -279,7 +279,7 @@ func DeleteNode(tx *gorm.DB,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unscoped causes the node to be fully removed from the database.
|
// Unscoped causes the node to be fully removed from the database.
|
||||||
if err := tx.Unscoped().Delete(&types.Node{}, node.ID).Error; err != nil {
|
if err := tx.Unscoped().Delete(&node).Error; err != nil {
|
||||||
return changed, err
|
return changed, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -661,7 +661,7 @@ func GenerateGivenName(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func DeleteExpiredEphemeralNodes(tx *gorm.DB,
|
func DeleteExpiredEphemeralNodes(tx *gorm.DB,
|
||||||
inactivityThreshold time.Duration,
|
inactivityThreshhold time.Duration,
|
||||||
) ([]types.NodeID, []types.NodeID) {
|
) ([]types.NodeID, []types.NodeID) {
|
||||||
users, err := ListUsers(tx)
|
users, err := ListUsers(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -679,7 +679,7 @@ func DeleteExpiredEphemeralNodes(tx *gorm.DB,
|
|||||||
for idx, node := range nodes {
|
for idx, node := range nodes {
|
||||||
if node.IsEphemeral() && node.LastSeen != nil &&
|
if node.IsEphemeral() && node.LastSeen != nil &&
|
||||||
time.Now().
|
time.Now().
|
||||||
After(node.LastSeen.Add(inactivityThreshold)) {
|
After(node.LastSeen.Add(inactivityThreshhold)) {
|
||||||
expired = append(expired, node.ID)
|
expired = append(expired, node.ID)
|
||||||
|
|
||||||
log.Info().
|
log.Info().
|
||||||
|
@@ -29,7 +29,6 @@ func (s *Suite) TestGetNode(c *check.C) {
|
|||||||
|
|
||||||
nodeKey := key.NewNode()
|
nodeKey := key.NewNode()
|
||||||
machineKey := key.NewMachine()
|
machineKey := key.NewMachine()
|
||||||
pakID := uint(pak.ID)
|
|
||||||
|
|
||||||
node := &types.Node{
|
node := &types.Node{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
@@ -38,10 +37,9 @@ func (s *Suite) TestGetNode(c *check.C) {
|
|||||||
Hostname: "testnode",
|
Hostname: "testnode",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
trx := db.DB.Save(node)
|
db.DB.Save(node)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
|
|
||||||
_, err = db.getNode("test", "testnode")
|
_, err = db.getNode("test", "testnode")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -60,7 +58,6 @@ func (s *Suite) TestGetNodeByID(c *check.C) {
|
|||||||
nodeKey := key.NewNode()
|
nodeKey := key.NewNode()
|
||||||
machineKey := key.NewMachine()
|
machineKey := key.NewMachine()
|
||||||
|
|
||||||
pakID := uint(pak.ID)
|
|
||||||
node := types.Node{
|
node := types.Node{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
MachineKey: machineKey.Public(),
|
MachineKey: machineKey.Public(),
|
||||||
@@ -68,10 +65,9 @@ func (s *Suite) TestGetNodeByID(c *check.C) {
|
|||||||
Hostname: "testnode",
|
Hostname: "testnode",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
trx := db.DB.Save(&node)
|
db.DB.Save(&node)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
|
|
||||||
_, err = db.GetNodeByID(0)
|
_, err = db.GetNodeByID(0)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -92,7 +88,6 @@ func (s *Suite) TestGetNodeByAnyNodeKey(c *check.C) {
|
|||||||
|
|
||||||
machineKey := key.NewMachine()
|
machineKey := key.NewMachine()
|
||||||
|
|
||||||
pakID := uint(pak.ID)
|
|
||||||
node := types.Node{
|
node := types.Node{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
MachineKey: machineKey.Public(),
|
MachineKey: machineKey.Public(),
|
||||||
@@ -100,10 +95,9 @@ func (s *Suite) TestGetNodeByAnyNodeKey(c *check.C) {
|
|||||||
Hostname: "testnode",
|
Hostname: "testnode",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
trx := db.DB.Save(&node)
|
db.DB.Save(&node)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
|
|
||||||
_, err = db.GetNodeByAnyKey(machineKey.Public(), nodeKey.Public(), oldNodeKey.Public())
|
_, err = db.GetNodeByAnyKey(machineKey.Public(), nodeKey.Public(), oldNodeKey.Public())
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -123,9 +117,9 @@ func (s *Suite) TestHardDeleteNode(c *check.C) {
|
|||||||
Hostname: "testnode3",
|
Hostname: "testnode3",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
|
AuthKeyID: uint(1),
|
||||||
}
|
}
|
||||||
trx := db.DB.Save(&node)
|
db.DB.Save(&node)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
|
|
||||||
_, err = db.DeleteNode(&node, xsync.NewMapOf[types.NodeID, bool]())
|
_, err = db.DeleteNode(&node, xsync.NewMapOf[types.NodeID, bool]())
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -144,7 +138,6 @@ func (s *Suite) TestListPeers(c *check.C) {
|
|||||||
_, err = db.GetNodeByID(0)
|
_, err = db.GetNodeByID(0)
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
|
|
||||||
pakID := uint(pak.ID)
|
|
||||||
for index := 0; index <= 10; index++ {
|
for index := 0; index <= 10; index++ {
|
||||||
nodeKey := key.NewNode()
|
nodeKey := key.NewNode()
|
||||||
machineKey := key.NewMachine()
|
machineKey := key.NewMachine()
|
||||||
@@ -156,10 +149,9 @@ func (s *Suite) TestListPeers(c *check.C) {
|
|||||||
Hostname: "testnode" + strconv.Itoa(index),
|
Hostname: "testnode" + strconv.Itoa(index),
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
trx := db.DB.Save(&node)
|
db.DB.Save(&node)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node0ByID, err := db.GetNodeByID(0)
|
node0ByID, err := db.GetNodeByID(0)
|
||||||
@@ -196,7 +188,6 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
|||||||
for index := 0; index <= 10; index++ {
|
for index := 0; index <= 10; index++ {
|
||||||
nodeKey := key.NewNode()
|
nodeKey := key.NewNode()
|
||||||
machineKey := key.NewMachine()
|
machineKey := key.NewMachine()
|
||||||
pakID := uint(stor[index%2].key.ID)
|
|
||||||
|
|
||||||
v4 := netip.MustParseAddr(fmt.Sprintf("100.64.0.%v", strconv.Itoa(index+1)))
|
v4 := netip.MustParseAddr(fmt.Sprintf("100.64.0.%v", strconv.Itoa(index+1)))
|
||||||
node := types.Node{
|
node := types.Node{
|
||||||
@@ -207,10 +198,9 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
|||||||
Hostname: "testnode" + strconv.Itoa(index),
|
Hostname: "testnode" + strconv.Itoa(index),
|
||||||
UserID: stor[index%2].user.ID,
|
UserID: stor[index%2].user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(stor[index%2].key.ID),
|
||||||
}
|
}
|
||||||
trx := db.DB.Save(&node)
|
db.DB.Save(&node)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
aclPolicy := &policy.ACLPolicy{
|
aclPolicy := &policy.ACLPolicy{
|
||||||
@@ -282,7 +272,6 @@ func (s *Suite) TestExpireNode(c *check.C) {
|
|||||||
|
|
||||||
nodeKey := key.NewNode()
|
nodeKey := key.NewNode()
|
||||||
machineKey := key.NewMachine()
|
machineKey := key.NewMachine()
|
||||||
pakID := uint(pak.ID)
|
|
||||||
|
|
||||||
node := &types.Node{
|
node := &types.Node{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
@@ -291,7 +280,7 @@ func (s *Suite) TestExpireNode(c *check.C) {
|
|||||||
Hostname: "testnode",
|
Hostname: "testnode",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
Expiry: &time.Time{},
|
Expiry: &time.Time{},
|
||||||
}
|
}
|
||||||
db.DB.Save(node)
|
db.DB.Save(node)
|
||||||
@@ -327,7 +316,6 @@ func (s *Suite) TestGenerateGivenName(c *check.C) {
|
|||||||
|
|
||||||
machineKey2 := key.NewMachine()
|
machineKey2 := key.NewMachine()
|
||||||
|
|
||||||
pakID := uint(pak.ID)
|
|
||||||
node := &types.Node{
|
node := &types.Node{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
MachineKey: machineKey.Public(),
|
MachineKey: machineKey.Public(),
|
||||||
@@ -336,11 +324,9 @@ func (s *Suite) TestGenerateGivenName(c *check.C) {
|
|||||||
GivenName: "hostname-1",
|
GivenName: "hostname-1",
|
||||||
UserID: user1.ID,
|
UserID: user1.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
|
db.DB.Save(node)
|
||||||
trx := db.DB.Save(node)
|
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
|
|
||||||
givenName, err := db.GenerateGivenName(machineKey2.Public(), "hostname-2")
|
givenName, err := db.GenerateGivenName(machineKey2.Public(), "hostname-2")
|
||||||
comment := check.Commentf("Same user, unique nodes, unique hostnames, no conflict")
|
comment := check.Commentf("Same user, unique nodes, unique hostnames, no conflict")
|
||||||
@@ -371,7 +357,6 @@ func (s *Suite) TestSetTags(c *check.C) {
|
|||||||
nodeKey := key.NewNode()
|
nodeKey := key.NewNode()
|
||||||
machineKey := key.NewMachine()
|
machineKey := key.NewMachine()
|
||||||
|
|
||||||
pakID := uint(pak.ID)
|
|
||||||
node := &types.Node{
|
node := &types.Node{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
MachineKey: machineKey.Public(),
|
MachineKey: machineKey.Public(),
|
||||||
@@ -379,11 +364,9 @@ func (s *Suite) TestSetTags(c *check.C) {
|
|||||||
Hostname: "testnode",
|
Hostname: "testnode",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
|
db.DB.Save(node)
|
||||||
trx := db.DB.Save(node)
|
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
|
|
||||||
// assign simple tags
|
// assign simple tags
|
||||||
sTags := []string{"tag:test", "tag:foo"}
|
sTags := []string{"tag:test", "tag:foo"}
|
||||||
@@ -393,7 +376,7 @@ func (s *Suite) TestSetTags(c *check.C) {
|
|||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(node.ForcedTags, check.DeepEquals, types.StringList(sTags))
|
c.Assert(node.ForcedTags, check.DeepEquals, types.StringList(sTags))
|
||||||
|
|
||||||
// assign duplicate tags, expect no errors but no doubles in DB
|
// assign duplicat tags, expect no errors but no doubles in DB
|
||||||
eTags := []string{"tag:bar", "tag:test", "tag:unknown", "tag:test"}
|
eTags := []string{"tag:bar", "tag:test", "tag:unknown", "tag:test"}
|
||||||
err = db.SetTags(node.ID, eTags)
|
err = db.SetTags(node.ID, eTags)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -565,7 +548,6 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
|
|||||||
route2 := netip.MustParsePrefix("10.11.0.0/24")
|
route2 := netip.MustParsePrefix("10.11.0.0/24")
|
||||||
|
|
||||||
v4 := netip.MustParseAddr("100.64.0.1")
|
v4 := netip.MustParseAddr("100.64.0.1")
|
||||||
pakID := uint(pak.ID)
|
|
||||||
node := types.Node{
|
node := types.Node{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
MachineKey: machineKey.Public(),
|
MachineKey: machineKey.Public(),
|
||||||
@@ -573,7 +555,7 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
|
|||||||
Hostname: "test",
|
Hostname: "test",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
Hostinfo: &tailcfg.Hostinfo{
|
Hostinfo: &tailcfg.Hostinfo{
|
||||||
RequestTags: []string{"tag:exit"},
|
RequestTags: []string{"tag:exit"},
|
||||||
RoutableIPs: []netip.Prefix{defaultRouteV4, defaultRouteV6, route1, route2},
|
RoutableIPs: []netip.Prefix{defaultRouteV4, defaultRouteV6, route1, route2},
|
||||||
@@ -581,8 +563,7 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
|
|||||||
IPv4: &v4,
|
IPv4: &v4,
|
||||||
}
|
}
|
||||||
|
|
||||||
trx := db.DB.Save(&node)
|
db.DB.Save(&node)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
|
|
||||||
sendUpdate, err := db.SaveNodeRoutes(&node)
|
sendUpdate, err := db.SaveNodeRoutes(&node)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
@@ -83,7 +83,7 @@ func CreatePreAuthKey(
|
|||||||
if !seenTags[tag] {
|
if !seenTags[tag] {
|
||||||
if err := tx.Save(&types.PreAuthKeyACLTag{PreAuthKeyID: key.ID, Tag: tag}).Error; err != nil {
|
if err := tx.Save(&types.PreAuthKeyACLTag{PreAuthKeyID: key.ID, Tag: tag}).Error; err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"failed to create key tag in the database: %w",
|
"failed to ceate key tag in the database: %w",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -197,10 +197,9 @@ func ValidatePreAuthKey(tx *gorm.DB, k string) (*types.PreAuthKey, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nodes := types.Nodes{}
|
nodes := types.Nodes{}
|
||||||
pakID := uint(pak.ID)
|
|
||||||
if err := tx.
|
if err := tx.
|
||||||
Preload("AuthKey").
|
Preload("AuthKey").
|
||||||
Where(&types.Node{AuthKeyID: &pakID}).
|
Where(&types.Node{AuthKeyID: uint(pak.ID)}).
|
||||||
Find(&nodes).Error; err != nil {
|
Find(&nodes).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -76,16 +76,14 @@ func (*Suite) TestAlreadyUsedKey(c *check.C) {
|
|||||||
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
pakID := uint(pak.ID)
|
|
||||||
node := types.Node{
|
node := types.Node{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
Hostname: "testest",
|
Hostname: "testest",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
trx := db.DB.Save(&node)
|
db.DB.Save(&node)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
|
|
||||||
key, err := db.ValidatePreAuthKey(pak.Key)
|
key, err := db.ValidatePreAuthKey(pak.Key)
|
||||||
c.Assert(err, check.Equals, ErrSingleUseAuthKeyHasBeenUsed)
|
c.Assert(err, check.Equals, ErrSingleUseAuthKeyHasBeenUsed)
|
||||||
@@ -99,16 +97,14 @@ func (*Suite) TestReusableBeingUsedKey(c *check.C) {
|
|||||||
pak, err := db.CreatePreAuthKey(user.Name, true, false, nil, nil)
|
pak, err := db.CreatePreAuthKey(user.Name, true, false, nil, nil)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
pakID := uint(pak.ID)
|
|
||||||
node := types.Node{
|
node := types.Node{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Hostname: "testest",
|
Hostname: "testest",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
trx := db.DB.Save(&node)
|
db.DB.Save(&node)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
|
|
||||||
key, err := db.ValidatePreAuthKey(pak.Key)
|
key, err := db.ValidatePreAuthKey(pak.Key)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -135,17 +131,15 @@ func (*Suite) TestEphemeralKeyReusable(c *check.C) {
|
|||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
now := time.Now().Add(-time.Second * 30)
|
now := time.Now().Add(-time.Second * 30)
|
||||||
pakID := uint(pak.ID)
|
|
||||||
node := types.Node{
|
node := types.Node{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
Hostname: "testest",
|
Hostname: "testest",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
LastSeen: &now,
|
LastSeen: &now,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
trx := db.DB.Save(&node)
|
db.DB.Save(&node)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
|
|
||||||
_, err = db.ValidatePreAuthKey(pak.Key)
|
_, err = db.ValidatePreAuthKey(pak.Key)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -171,14 +165,13 @@ func (*Suite) TestEphemeralKeyNotReusable(c *check.C) {
|
|||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
now := time.Now().Add(-time.Second * 30)
|
now := time.Now().Add(-time.Second * 30)
|
||||||
pakId := uint(pak.ID)
|
|
||||||
node := types.Node{
|
node := types.Node{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
Hostname: "testest",
|
Hostname: "testest",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
LastSeen: &now,
|
LastSeen: &now,
|
||||||
AuthKeyID: &pakId,
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
db.DB.Save(&node)
|
db.DB.Save(&node)
|
||||||
|
|
||||||
|
@@ -43,17 +43,15 @@ func (s *Suite) TestGetRoutes(c *check.C) {
|
|||||||
RoutableIPs: []netip.Prefix{route},
|
RoutableIPs: []netip.Prefix{route},
|
||||||
}
|
}
|
||||||
|
|
||||||
pakID := uint(pak.ID)
|
|
||||||
node := types.Node{
|
node := types.Node{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
Hostname: "test_get_route_node",
|
Hostname: "test_get_route_node",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
Hostinfo: &hostInfo,
|
Hostinfo: &hostInfo,
|
||||||
}
|
}
|
||||||
trx := db.DB.Save(&node)
|
db.DB.Save(&node)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
|
|
||||||
su, err := db.SaveNodeRoutes(&node)
|
su, err := db.SaveNodeRoutes(&node)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -95,17 +93,15 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) {
|
|||||||
RoutableIPs: []netip.Prefix{route, route2},
|
RoutableIPs: []netip.Prefix{route, route2},
|
||||||
}
|
}
|
||||||
|
|
||||||
pakID := uint(pak.ID)
|
|
||||||
node := types.Node{
|
node := types.Node{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
Hostname: "test_enable_route_node",
|
Hostname: "test_enable_route_node",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
Hostinfo: &hostInfo,
|
Hostinfo: &hostInfo,
|
||||||
}
|
}
|
||||||
trx := db.DB.Save(&node)
|
db.DB.Save(&node)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
|
|
||||||
sendUpdate, err := db.SaveNodeRoutes(&node)
|
sendUpdate, err := db.SaveNodeRoutes(&node)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -169,17 +165,15 @@ func (s *Suite) TestIsUniquePrefix(c *check.C) {
|
|||||||
hostInfo1 := tailcfg.Hostinfo{
|
hostInfo1 := tailcfg.Hostinfo{
|
||||||
RoutableIPs: []netip.Prefix{route, route2},
|
RoutableIPs: []netip.Prefix{route, route2},
|
||||||
}
|
}
|
||||||
pakID := uint(pak.ID)
|
|
||||||
node1 := types.Node{
|
node1 := types.Node{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Hostname: "test_enable_route_node",
|
Hostname: "test_enable_route_node",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
Hostinfo: &hostInfo1,
|
Hostinfo: &hostInfo1,
|
||||||
}
|
}
|
||||||
trx := db.DB.Save(&node1)
|
db.DB.Save(&node1)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
|
|
||||||
sendUpdate, err := db.SaveNodeRoutes(&node1)
|
sendUpdate, err := db.SaveNodeRoutes(&node1)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -199,7 +193,7 @@ func (s *Suite) TestIsUniquePrefix(c *check.C) {
|
|||||||
Hostname: "test_enable_route_node",
|
Hostname: "test_enable_route_node",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
Hostinfo: &hostInfo2,
|
Hostinfo: &hostInfo2,
|
||||||
}
|
}
|
||||||
db.DB.Save(&node2)
|
db.DB.Save(&node2)
|
||||||
@@ -253,18 +247,16 @@ func (s *Suite) TestDeleteRoutes(c *check.C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
pakID := uint(pak.ID)
|
|
||||||
node1 := types.Node{
|
node1 := types.Node{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Hostname: "test_enable_route_node",
|
Hostname: "test_enable_route_node",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
Hostinfo: &hostInfo1,
|
Hostinfo: &hostInfo1,
|
||||||
LastSeen: &now,
|
LastSeen: &now,
|
||||||
}
|
}
|
||||||
trx := db.DB.Save(&node1)
|
db.DB.Save(&node1)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
|
|
||||||
sendUpdate, err := db.SaveNodeRoutes(&node1)
|
sendUpdate, err := db.SaveNodeRoutes(&node1)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -625,16 +617,7 @@ func TestFailoverNodeRoutesIfNeccessary(t *testing.T) {
|
|||||||
|
|
||||||
db := dbForTest(t, tt.name)
|
db := dbForTest(t, tt.name)
|
||||||
|
|
||||||
user := types.User{Name: tt.name}
|
|
||||||
if err := db.DB.Save(&user).Error; err != nil {
|
|
||||||
t.Fatalf("failed to create user: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, route := range tt.routes {
|
for _, route := range tt.routes {
|
||||||
route.Node.User = user
|
|
||||||
if err := db.DB.Save(&route.Node).Error; err != nil {
|
|
||||||
t.Fatalf("failed to create node: %s", err)
|
|
||||||
}
|
|
||||||
if err := db.DB.Save(&route).Error; err != nil {
|
if err := db.DB.Save(&route).Error; err != nil {
|
||||||
t.Fatalf("failed to create route: %s", err)
|
t.Fatalf("failed to create route: %s", err)
|
||||||
}
|
}
|
||||||
@@ -1030,16 +1013,8 @@ func TestFailoverRouteTx(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
db := dbForTest(t, tt.name)
|
db := dbForTest(t, tt.name)
|
||||||
user := types.User{Name: "test"}
|
|
||||||
if err := db.DB.Save(&user).Error; err != nil {
|
|
||||||
t.Fatalf("failed to create user: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, route := range tt.routes {
|
for _, route := range tt.routes {
|
||||||
route.Node.User = user
|
|
||||||
if err := db.DB.Save(&route.Node).Error; err != nil {
|
|
||||||
t.Fatalf("failed to create node: %s", err)
|
|
||||||
}
|
|
||||||
if err := db.DB.Save(&route).Error; err != nil {
|
if err := db.DB.Save(&route).Error; err != nil {
|
||||||
t.Fatalf("failed to create route: %s", err)
|
t.Fatalf("failed to create route: %s", err)
|
||||||
}
|
}
|
||||||
|
@@ -46,16 +46,14 @@ func (s *Suite) TestDestroyUserErrors(c *check.C) {
|
|||||||
pak, err = db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
pak, err = db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
pakID := uint(pak.ID)
|
|
||||||
node := types.Node{
|
node := types.Node{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
Hostname: "testnode",
|
Hostname: "testnode",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
trx := db.DB.Save(&node)
|
db.DB.Save(&node)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
|
|
||||||
err = db.DestroyUser("test")
|
err = db.DestroyUser("test")
|
||||||
c.Assert(err, check.Equals, ErrUserStillHasNodes)
|
c.Assert(err, check.Equals, ErrUserStillHasNodes)
|
||||||
@@ -100,16 +98,14 @@ func (s *Suite) TestSetMachineUser(c *check.C) {
|
|||||||
pak, err := db.CreatePreAuthKey(oldUser.Name, false, false, nil, nil)
|
pak, err := db.CreatePreAuthKey(oldUser.Name, false, false, nil, nil)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
pakID := uint(pak.ID)
|
|
||||||
node := types.Node{
|
node := types.Node{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
Hostname: "testnode",
|
Hostname: "testnode",
|
||||||
UserID: oldUser.ID,
|
UserID: oldUser.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
AuthKeyID: &pakID,
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
trx := db.DB.Save(&node)
|
db.DB.Save(&node)
|
||||||
c.Assert(trx.Error, check.IsNil)
|
|
||||||
c.Assert(node.UserID, check.Equals, oldUser.ID)
|
c.Assert(node.UserID, check.Equals, oldUser.ID)
|
||||||
|
|
||||||
err = db.AssignNodeToUser(&node, newUser.Name)
|
err = db.AssignNodeToUser(&node, newUser.Name)
|
||||||
|
@@ -204,7 +204,7 @@ func DERPProbeHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DERPBootstrapDNSHandler implements the /bootstrap-dns endpoint
|
// DERPBootstrapDNSHandler implements the /bootsrap-dns endpoint
|
||||||
// Described in https://github.com/tailscale/tailscale/issues/1405,
|
// Described in https://github.com/tailscale/tailscale/issues/1405,
|
||||||
// this endpoint provides a way to help a client when it fails to start up
|
// this endpoint provides a way to help a client when it fails to start up
|
||||||
// because its DNS are broken.
|
// because its DNS are broken.
|
||||||
|
@@ -187,9 +187,10 @@ func Test_fullMapResponse(t *testing.T) {
|
|||||||
UserID: 0,
|
UserID: 0,
|
||||||
User: types.User{Name: "mini"},
|
User: types.User{Name: "mini"},
|
||||||
ForcedTags: []string{},
|
ForcedTags: []string{},
|
||||||
AuthKey: &types.PreAuthKey{},
|
AuthKeyID: 0,
|
||||||
LastSeen: &lastSeen,
|
AuthKey: &types.PreAuthKey{},
|
||||||
Expiry: &expire,
|
LastSeen: &lastSeen,
|
||||||
|
Expiry: &expire,
|
||||||
Hostinfo: &tailcfg.Hostinfo{},
|
Hostinfo: &tailcfg.Hostinfo{},
|
||||||
Routes: []types.Route{
|
Routes: []types.Route{
|
||||||
{
|
{
|
||||||
|
@@ -97,6 +97,7 @@ func TestTailNode(t *testing.T) {
|
|||||||
Name: "mini",
|
Name: "mini",
|
||||||
},
|
},
|
||||||
ForcedTags: []string{},
|
ForcedTags: []string{},
|
||||||
|
AuthKeyID: 0,
|
||||||
AuthKey: &types.PreAuthKey{},
|
AuthKey: &types.PreAuthKey{},
|
||||||
LastSeen: &lastSeen,
|
LastSeen: &lastSeen,
|
||||||
Expiry: &expire,
|
Expiry: &expire,
|
||||||
|
@@ -7,23 +7,8 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
"tailscale.com/envknob"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var debugHighCardinalityMetrics = envknob.Bool("HEADSCALE_DEBUG_HIGH_CARDINALITY_METRICS")
|
|
||||||
|
|
||||||
var mapResponseLastSentSeconds *prometheus.GaugeVec
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if debugHighCardinalityMetrics {
|
|
||||||
mapResponseLastSentSeconds = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
|
||||||
Namespace: prometheusNamespace,
|
|
||||||
Name: "mapresponse_last_sent_seconds",
|
|
||||||
Help: "last sent metric to node.id",
|
|
||||||
}, []string{"type", "id"})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const prometheusNamespace = "headscale"
|
const prometheusNamespace = "headscale"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -52,16 +37,16 @@ var (
|
|||||||
Name: "mapresponse_readonly_requests_total",
|
Name: "mapresponse_readonly_requests_total",
|
||||||
Help: "total count of readonly requests received",
|
Help: "total count of readonly requests received",
|
||||||
}, []string{"status"})
|
}, []string{"status"})
|
||||||
mapResponseEnded = promauto.NewCounterVec(prometheus.CounterOpts{
|
mapResponseSessions = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
Namespace: prometheusNamespace,
|
Namespace: prometheusNamespace,
|
||||||
Name: "mapresponse_ended_total",
|
Name: "mapresponse_current_sessions_total",
|
||||||
Help: "total count of new mapsessions ended",
|
Help: "total count open map response sessions",
|
||||||
|
})
|
||||||
|
mapResponseRejected = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Namespace: prometheusNamespace,
|
||||||
|
Name: "mapresponse_rejected_new_sessions_total",
|
||||||
|
Help: "total count of new mapsessions rejected",
|
||||||
}, []string{"reason"})
|
}, []string{"reason"})
|
||||||
mapResponseClosed = promauto.NewCounterVec(prometheus.CounterOpts{
|
|
||||||
Namespace: prometheusNamespace,
|
|
||||||
Name: "mapresponse_closed_total",
|
|
||||||
Help: "total count of calls to mapresponse close",
|
|
||||||
}, []string{"return"})
|
|
||||||
httpDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
httpDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||||
Namespace: prometheusNamespace,
|
Namespace: prometheusNamespace,
|
||||||
Name: "http_duration_seconds",
|
Name: "http_duration_seconds",
|
||||||
|
@@ -226,17 +226,68 @@ func (ns *noiseServer) NoisePollNetMapHandler(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Str("handler", "NoisePollNetMap").
|
Str("handler", "NoisePollNetMap").
|
||||||
|
Uint64("node.id", node.ID.Uint64()).
|
||||||
Msgf("Failed to fetch node from the database with node key: %s", mapRequest.NodeKey.String())
|
Msgf("Failed to fetch node from the database with node key: %s", mapRequest.NodeKey.String())
|
||||||
http.Error(writer, "Internal error", http.StatusInternalServerError)
|
http.Error(writer, "Internal error", http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sess := ns.headscale.newMapSession(req.Context(), mapRequest, writer, node)
|
sess := ns.headscale.newMapSession(req.Context(), mapRequest, writer, node)
|
||||||
|
|
||||||
sess.tracef("a node sending a MapRequest with Noise protocol")
|
sess.tracef("a node sending a MapRequest with Noise protocol")
|
||||||
if !sess.isStreaming() {
|
|
||||||
sess.serve()
|
// If a streaming mapSession exists for this node, close it
|
||||||
} else {
|
// and start a new one.
|
||||||
sess.serveLongPoll()
|
if sess.isStreaming() {
|
||||||
|
sess.tracef("aquiring lock to check stream")
|
||||||
|
|
||||||
|
ns.headscale.mapSessionMu.Lock()
|
||||||
|
if _, ok := ns.headscale.mapSessions[node.ID]; ok {
|
||||||
|
// NOTE/TODO(kradalby): From how I understand the protocol, when
|
||||||
|
// a client connects with stream=true, and already has a streaming
|
||||||
|
// connection open, the correct way is to close the current channel
|
||||||
|
// and replace it. However, I cannot manage to get that working with
|
||||||
|
// some sort of lock/block happening on the cancelCh in the streaming
|
||||||
|
// session.
|
||||||
|
// Not closing the channel and replacing it puts us in a weird state
|
||||||
|
// which keeps a ghost stream open, receiving keep alives, but no updates.
|
||||||
|
//
|
||||||
|
// Typically a new connection is opened when one exists as a client which
|
||||||
|
// is already authenticated reconnects (e.g. down, then up). The client will
|
||||||
|
// start auth and streaming at the same time, and then cancel the streaming
|
||||||
|
// when the auth has finished successfully, opening a new connection.
|
||||||
|
//
|
||||||
|
// As a work-around to not replacing, abusing the clients "resilience"
|
||||||
|
// by reject the new connection which will cause the client to immediately
|
||||||
|
// reconnect and "fix" the issue, as the other connection typically has been
|
||||||
|
// closed, meaning there is nothing to replace.
|
||||||
|
//
|
||||||
|
// sess.infof("node has an open stream(%p), replacing with %p", oldSession, sess)
|
||||||
|
// oldSession.close()
|
||||||
|
|
||||||
|
defer ns.headscale.mapSessionMu.Unlock()
|
||||||
|
|
||||||
|
sess.infof("node has an open stream(%p), rejecting new stream", sess)
|
||||||
|
mapResponseRejected.WithLabelValues("exists").Inc()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ns.headscale.mapSessions[node.ID] = sess
|
||||||
|
mapResponseSessions.Inc()
|
||||||
|
ns.headscale.mapSessionMu.Unlock()
|
||||||
|
sess.tracef("releasing lock to check stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
sess.serve()
|
||||||
|
|
||||||
|
if sess.isStreaming() {
|
||||||
|
sess.tracef("aquiring lock to remove stream")
|
||||||
|
ns.headscale.mapSessionMu.Lock()
|
||||||
|
defer ns.headscale.mapSessionMu.Unlock()
|
||||||
|
|
||||||
|
delete(ns.headscale.mapSessions, node.ID)
|
||||||
|
mapResponseSessions.Dec()
|
||||||
|
|
||||||
|
sess.tracef("releasing lock to remove stream")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,66 +3,25 @@ package notifier
|
|||||||
import (
|
import (
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
"tailscale.com/envknob"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const prometheusNamespace = "headscale"
|
const prometheusNamespace = "headscale"
|
||||||
|
|
||||||
var debugHighCardinalityMetrics = envknob.Bool("HEADSCALE_DEBUG_HIGH_CARDINALITY_METRICS")
|
|
||||||
|
|
||||||
var notifierUpdateSent *prometheus.CounterVec
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if debugHighCardinalityMetrics {
|
|
||||||
notifierUpdateSent = promauto.NewCounterVec(prometheus.CounterOpts{
|
|
||||||
Namespace: prometheusNamespace,
|
|
||||||
Name: "notifier_update_sent_total",
|
|
||||||
Help: "total count of update sent on nodes channel",
|
|
||||||
}, []string{"status", "type", "trigger", "id"})
|
|
||||||
} else {
|
|
||||||
notifierUpdateSent = promauto.NewCounterVec(prometheus.CounterOpts{
|
|
||||||
Namespace: prometheusNamespace,
|
|
||||||
Name: "notifier_update_sent_total",
|
|
||||||
Help: "total count of update sent on nodes channel",
|
|
||||||
}, []string{"status", "type", "trigger"})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
notifierWaitersForLock = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
|
||||||
Namespace: prometheusNamespace,
|
|
||||||
Name: "notifier_waiters_for_lock",
|
|
||||||
Help: "gauge of waiters for the notifier lock",
|
|
||||||
}, []string{"type", "action"})
|
|
||||||
notifierWaitForLock = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
notifierWaitForLock = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||||
Namespace: prometheusNamespace,
|
Namespace: prometheusNamespace,
|
||||||
Name: "notifier_wait_for_lock_seconds",
|
Name: "notifier_wait_for_lock_seconds",
|
||||||
Help: "histogram of time spent waiting for the notifier lock",
|
Help: "histogram of time spent waiting for the notifier lock",
|
||||||
Buckets: []float64{0.001, 0.01, 0.1, 0.3, 0.5, 1, 3, 5, 10},
|
Buckets: []float64{0.001, 0.01, 0.1, 0.3, 0.5, 1, 3, 5, 10},
|
||||||
}, []string{"action"})
|
}, []string{"action"})
|
||||||
notifierUpdateReceived = promauto.NewCounterVec(prometheus.CounterOpts{
|
notifierUpdateSent = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
Namespace: prometheusNamespace,
|
Namespace: prometheusNamespace,
|
||||||
Name: "notifier_update_received_total",
|
Name: "notifier_update_sent_total",
|
||||||
Help: "total count of updates received by notifier",
|
Help: "total count of update sent on nodes channel",
|
||||||
}, []string{"type", "trigger"})
|
}, []string{"status", "type"})
|
||||||
notifierNodeUpdateChans = promauto.NewGauge(prometheus.GaugeOpts{
|
notifierNodeUpdateChans = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
Namespace: prometheusNamespace,
|
Namespace: prometheusNamespace,
|
||||||
Name: "notifier_open_channels_total",
|
Name: "notifier_open_channels_total",
|
||||||
Help: "total count open channels in notifier",
|
Help: "total count open channels in notifier",
|
||||||
})
|
})
|
||||||
notifierBatcherWaitersForLock = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
|
||||||
Namespace: prometheusNamespace,
|
|
||||||
Name: "notifier_batcher_waiters_for_lock",
|
|
||||||
Help: "gauge of waiters for the notifier batcher lock",
|
|
||||||
}, []string{"type", "action"})
|
|
||||||
notifierBatcherChanges = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
|
||||||
Namespace: prometheusNamespace,
|
|
||||||
Name: "notifier_batcher_changes_pending",
|
|
||||||
Help: "gauge of full changes pending in the notifier batcher",
|
|
||||||
}, []string{})
|
|
||||||
notifierBatcherPatches = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
|
||||||
Namespace: prometheusNamespace,
|
|
||||||
Name: "notifier_batcher_patches_pending",
|
|
||||||
Help: "gauge of patches pending in the notifier batcher",
|
|
||||||
}, []string{})
|
|
||||||
)
|
)
|
||||||
|
@@ -3,7 +3,7 @@ package notifier
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -11,118 +11,74 @@ import (
|
|||||||
"github.com/juanfont/headscale/hscontrol/types"
|
"github.com/juanfont/headscale/hscontrol/types"
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/sasha-s/go-deadlock"
|
|
||||||
"tailscale.com/envknob"
|
|
||||||
"tailscale.com/tailcfg"
|
|
||||||
"tailscale.com/util/set"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var debugDeadlock = envknob.Bool("HEADSCALE_DEBUG_DEADLOCK")
|
|
||||||
var debugDeadlockTimeout = envknob.RegisterDuration("HEADSCALE_DEBUG_DEADLOCK_TIMEOUT")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
deadlock.Opts.Disable = !debugDeadlock
|
|
||||||
if debugDeadlock {
|
|
||||||
deadlock.Opts.DeadlockTimeout = debugDeadlockTimeout()
|
|
||||||
deadlock.Opts.PrintAllCurrentGoroutines = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Notifier struct {
|
type Notifier struct {
|
||||||
l deadlock.Mutex
|
l sync.RWMutex
|
||||||
nodes map[types.NodeID]chan<- types.StateUpdate
|
nodes map[types.NodeID]chan<- types.StateUpdate
|
||||||
connected *xsync.MapOf[types.NodeID, bool]
|
connected *xsync.MapOf[types.NodeID, bool]
|
||||||
b *batcher
|
|
||||||
cfg *types.Config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNotifier(cfg *types.Config) *Notifier {
|
func NewNotifier() *Notifier {
|
||||||
n := &Notifier{
|
return &Notifier{
|
||||||
nodes: make(map[types.NodeID]chan<- types.StateUpdate),
|
nodes: make(map[types.NodeID]chan<- types.StateUpdate),
|
||||||
connected: xsync.NewMapOf[types.NodeID, bool](),
|
connected: xsync.NewMapOf[types.NodeID, bool](),
|
||||||
cfg: cfg,
|
|
||||||
}
|
}
|
||||||
b := newBatcher(cfg.Tuning.BatchChangeDelay, n)
|
|
||||||
n.b = b
|
|
||||||
|
|
||||||
go b.doWork()
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close stops the batcher inside the notifier.
|
|
||||||
func (n *Notifier) Close() {
|
|
||||||
n.b.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notifier) tracef(nID types.NodeID, msg string, args ...any) {
|
|
||||||
log.Trace().
|
|
||||||
Uint64("node.id", nID.Uint64()).
|
|
||||||
Int("open_chans", len(n.nodes)).Msgf(msg, args...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notifier) AddNode(nodeID types.NodeID, c chan<- types.StateUpdate) {
|
func (n *Notifier) AddNode(nodeID types.NodeID, c chan<- types.StateUpdate) {
|
||||||
|
log.Trace().Caller().Uint64("node.id", nodeID.Uint64()).Msg("acquiring lock to add node")
|
||||||
|
defer log.Trace().
|
||||||
|
Caller().
|
||||||
|
Uint64("node.id", nodeID.Uint64()).
|
||||||
|
Msg("releasing lock to add node")
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
notifierWaitersForLock.WithLabelValues("lock", "add").Inc()
|
|
||||||
n.l.Lock()
|
n.l.Lock()
|
||||||
defer n.l.Unlock()
|
defer n.l.Unlock()
|
||||||
notifierWaitersForLock.WithLabelValues("lock", "add").Dec()
|
|
||||||
notifierWaitForLock.WithLabelValues("add").Observe(time.Since(start).Seconds())
|
notifierWaitForLock.WithLabelValues("add").Observe(time.Since(start).Seconds())
|
||||||
|
|
||||||
// If a channel exists, it means the node has opened a new
|
|
||||||
// connection. Close the old channel and replace it.
|
|
||||||
if curr, ok := n.nodes[nodeID]; ok {
|
|
||||||
n.tracef(nodeID, "channel present, closing and replacing")
|
|
||||||
close(curr)
|
|
||||||
}
|
|
||||||
|
|
||||||
n.nodes[nodeID] = c
|
n.nodes[nodeID] = c
|
||||||
n.connected.Store(nodeID, true)
|
n.connected.Store(nodeID, true)
|
||||||
|
|
||||||
n.tracef(nodeID, "added new channel")
|
log.Trace().
|
||||||
|
Uint64("node.id", nodeID.Uint64()).
|
||||||
|
Int("open_chans", len(n.nodes)).
|
||||||
|
Msg("Added new channel")
|
||||||
notifierNodeUpdateChans.Inc()
|
notifierNodeUpdateChans.Inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveNode removes a node and a given channel from the notifier.
|
func (n *Notifier) RemoveNode(nodeID types.NodeID) {
|
||||||
// It checks that the channel is the same as currently being updated
|
log.Trace().Caller().Uint64("node.id", nodeID.Uint64()).Msg("acquiring lock to remove node")
|
||||||
// and ignores the removal if it is not.
|
defer log.Trace().
|
||||||
// RemoveNode reports if the node/chan was removed.
|
Caller().
|
||||||
func (n *Notifier) RemoveNode(nodeID types.NodeID, c chan<- types.StateUpdate) bool {
|
Uint64("node.id", nodeID.Uint64()).
|
||||||
|
Msg("releasing lock to remove node")
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
notifierWaitersForLock.WithLabelValues("lock", "remove").Inc()
|
|
||||||
n.l.Lock()
|
n.l.Lock()
|
||||||
defer n.l.Unlock()
|
defer n.l.Unlock()
|
||||||
notifierWaitersForLock.WithLabelValues("lock", "remove").Dec()
|
|
||||||
notifierWaitForLock.WithLabelValues("remove").Observe(time.Since(start).Seconds())
|
notifierWaitForLock.WithLabelValues("remove").Observe(time.Since(start).Seconds())
|
||||||
|
|
||||||
if len(n.nodes) == 0 {
|
if len(n.nodes) == 0 {
|
||||||
return true
|
return
|
||||||
}
|
|
||||||
|
|
||||||
// If the channel exist, but it does not belong
|
|
||||||
// to the caller, ignore.
|
|
||||||
if curr, ok := n.nodes[nodeID]; ok {
|
|
||||||
if curr != c {
|
|
||||||
n.tracef(nodeID, "channel has been replaced, not removing")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(n.nodes, nodeID)
|
delete(n.nodes, nodeID)
|
||||||
n.connected.Store(nodeID, false)
|
n.connected.Store(nodeID, false)
|
||||||
|
|
||||||
n.tracef(nodeID, "removed channel")
|
log.Trace().
|
||||||
|
Uint64("node.id", nodeID.Uint64()).
|
||||||
|
Int("open_chans", len(n.nodes)).
|
||||||
|
Msg("Removed channel")
|
||||||
notifierNodeUpdateChans.Dec()
|
notifierNodeUpdateChans.Dec()
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsConnected reports if a node is connected to headscale and has a
|
// IsConnected reports if a node is connected to headscale and has a
|
||||||
// poll session open.
|
// poll session open.
|
||||||
func (n *Notifier) IsConnected(nodeID types.NodeID) bool {
|
func (n *Notifier) IsConnected(nodeID types.NodeID) bool {
|
||||||
notifierWaitersForLock.WithLabelValues("lock", "conncheck").Inc()
|
n.l.RLock()
|
||||||
n.l.Lock()
|
defer n.l.RUnlock()
|
||||||
defer n.l.Unlock()
|
|
||||||
notifierWaitersForLock.WithLabelValues("lock", "conncheck").Dec()
|
|
||||||
|
|
||||||
if val, ok := n.connected.Load(nodeID); ok {
|
if val, ok := n.connected.Load(nodeID); ok {
|
||||||
return val
|
return val
|
||||||
@@ -152,8 +108,13 @@ func (n *Notifier) NotifyWithIgnore(
|
|||||||
update types.StateUpdate,
|
update types.StateUpdate,
|
||||||
ignoreNodeIDs ...types.NodeID,
|
ignoreNodeIDs ...types.NodeID,
|
||||||
) {
|
) {
|
||||||
notifierUpdateReceived.WithLabelValues(update.Type.String(), types.NotifyOriginKey.Value(ctx)).Inc()
|
for nodeID := range n.nodes {
|
||||||
n.b.addOrPassthrough(update)
|
if slices.Contains(ignoreNodeIDs, nodeID) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
n.NotifyByNodeID(ctx, update, nodeID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notifier) NotifyByNodeID(
|
func (n *Notifier) NotifyByNodeID(
|
||||||
@@ -161,11 +122,15 @@ func (n *Notifier) NotifyByNodeID(
|
|||||||
update types.StateUpdate,
|
update types.StateUpdate,
|
||||||
nodeID types.NodeID,
|
nodeID types.NodeID,
|
||||||
) {
|
) {
|
||||||
|
log.Trace().Caller().Str("type", update.Type.String()).Msg("acquiring lock to notify")
|
||||||
|
defer log.Trace().
|
||||||
|
Caller().
|
||||||
|
Str("type", update.Type.String()).
|
||||||
|
Msg("releasing lock, finished notifying")
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
notifierWaitersForLock.WithLabelValues("lock", "notify").Inc()
|
n.l.RLock()
|
||||||
n.l.Lock()
|
defer n.l.RUnlock()
|
||||||
defer n.l.Unlock()
|
|
||||||
notifierWaitersForLock.WithLabelValues("lock", "notify").Dec()
|
|
||||||
notifierWaitForLock.WithLabelValues("notify").Observe(time.Since(start).Seconds())
|
notifierWaitForLock.WithLabelValues("notify").Observe(time.Since(start).Seconds())
|
||||||
|
|
||||||
if c, ok := n.nodes[nodeID]; ok {
|
if c, ok := n.nodes[nodeID]; ok {
|
||||||
@@ -174,264 +139,41 @@ func (n *Notifier) NotifyByNodeID(
|
|||||||
log.Error().
|
log.Error().
|
||||||
Err(ctx.Err()).
|
Err(ctx.Err()).
|
||||||
Uint64("node.id", nodeID.Uint64()).
|
Uint64("node.id", nodeID.Uint64()).
|
||||||
Any("origin", types.NotifyOriginKey.Value(ctx)).
|
Any("origin", ctx.Value("origin")).
|
||||||
Any("origin-hostname", types.NotifyHostnameKey.Value(ctx)).
|
Any("origin-hostname", ctx.Value("hostname")).
|
||||||
Msgf("update not sent, context cancelled")
|
Msgf("update not sent, context cancelled")
|
||||||
if debugHighCardinalityMetrics {
|
notifierUpdateSent.WithLabelValues("cancelled", update.Type.String()).Inc()
|
||||||
notifierUpdateSent.WithLabelValues("cancelled", update.Type.String(), types.NotifyOriginKey.Value(ctx), nodeID.String()).Inc()
|
|
||||||
} else {
|
|
||||||
notifierUpdateSent.WithLabelValues("cancelled", update.Type.String(), types.NotifyOriginKey.Value(ctx)).Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
case c <- update:
|
case c <- update:
|
||||||
n.tracef(nodeID, "update successfully sent on chan, origin: %s, origin-hostname: %s", ctx.Value("origin"), ctx.Value("hostname"))
|
log.Trace().
|
||||||
if debugHighCardinalityMetrics {
|
Uint64("node.id", nodeID.Uint64()).
|
||||||
notifierUpdateSent.WithLabelValues("ok", update.Type.String(), types.NotifyOriginKey.Value(ctx), nodeID.String()).Inc()
|
Any("origin", ctx.Value("origin")).
|
||||||
} else {
|
Any("origin-hostname", ctx.Value("hostname")).
|
||||||
notifierUpdateSent.WithLabelValues("ok", update.Type.String(), types.NotifyOriginKey.Value(ctx)).Inc()
|
Msgf("update successfully sent on chan")
|
||||||
}
|
notifierUpdateSent.WithLabelValues("ok", update.Type.String()).Inc()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notifier) sendAll(update types.StateUpdate) {
|
|
||||||
start := time.Now()
|
|
||||||
notifierWaitersForLock.WithLabelValues("lock", "send-all").Inc()
|
|
||||||
n.l.Lock()
|
|
||||||
defer n.l.Unlock()
|
|
||||||
notifierWaitersForLock.WithLabelValues("lock", "send-all").Dec()
|
|
||||||
notifierWaitForLock.WithLabelValues("send-all").Observe(time.Since(start).Seconds())
|
|
||||||
|
|
||||||
for id, c := range n.nodes {
|
|
||||||
// Whenever an update is sent to all nodes, there is a chance that the node
|
|
||||||
// has disconnected and the goroutine that was supposed to consume the update
|
|
||||||
// has shut down the channel and is waiting for the lock held here in RemoveNode.
|
|
||||||
// This means that there is potential for a deadlock which would stop all updates
|
|
||||||
// going out to clients. This timeout prevents that from happening by moving on to the
|
|
||||||
// next node if the context is cancelled. Afther sendAll releases the lock, the add/remove
|
|
||||||
// call will succeed and the update will go to the correct nodes on the next call.
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), n.cfg.Tuning.NotifierSendTimeout)
|
|
||||||
defer cancel()
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
log.Error().
|
|
||||||
Err(ctx.Err()).
|
|
||||||
Uint64("node.id", id.Uint64()).
|
|
||||||
Msgf("update not sent, context cancelled")
|
|
||||||
if debugHighCardinalityMetrics {
|
|
||||||
notifierUpdateSent.WithLabelValues("cancelled", update.Type.String(), "send-all", id.String()).Inc()
|
|
||||||
} else {
|
|
||||||
notifierUpdateSent.WithLabelValues("cancelled", update.Type.String(), "send-all").Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
case c <- update:
|
|
||||||
if debugHighCardinalityMetrics {
|
|
||||||
notifierUpdateSent.WithLabelValues("ok", update.Type.String(), "send-all", id.String()).Inc()
|
|
||||||
} else {
|
|
||||||
notifierUpdateSent.WithLabelValues("ok", update.Type.String(), "send-all").Inc()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notifier) String() string {
|
func (n *Notifier) String() string {
|
||||||
notifierWaitersForLock.WithLabelValues("lock", "string").Inc()
|
n.l.RLock()
|
||||||
n.l.Lock()
|
defer n.l.RUnlock()
|
||||||
defer n.l.Unlock()
|
|
||||||
notifierWaitersForLock.WithLabelValues("lock", "string").Dec()
|
|
||||||
|
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
fmt.Fprintf(&b, "chans (%d):\n", len(n.nodes))
|
b.WriteString("chans:\n")
|
||||||
|
|
||||||
var keys []types.NodeID
|
for k, v := range n.nodes {
|
||||||
n.connected.Range(func(key types.NodeID, value bool) bool {
|
fmt.Fprintf(&b, "\t%d: %p\n", k, v)
|
||||||
keys = append(keys, key)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
sort.Slice(keys, func(i, j int) bool {
|
|
||||||
return keys[i] < keys[j]
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, key := range keys {
|
|
||||||
fmt.Fprintf(&b, "\t%d: %p\n", key, n.nodes[key])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b.WriteString("\n")
|
b.WriteString("\n")
|
||||||
fmt.Fprintf(&b, "connected (%d):\n", len(n.nodes))
|
b.WriteString("connected:\n")
|
||||||
|
|
||||||
for _, key := range keys {
|
n.connected.Range(func(k types.NodeID, v bool) bool {
|
||||||
val, _ := n.connected.Load(key)
|
fmt.Fprintf(&b, "\t%d: %t\n", k, v)
|
||||||
fmt.Fprintf(&b, "\t%d: %t\n", key, val)
|
return true
|
||||||
}
|
})
|
||||||
|
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
type batcher struct {
|
|
||||||
tick *time.Ticker
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
|
|
||||||
cancelCh chan struct{}
|
|
||||||
|
|
||||||
changedNodeIDs set.Slice[types.NodeID]
|
|
||||||
nodesChanged bool
|
|
||||||
patches map[types.NodeID]tailcfg.PeerChange
|
|
||||||
patchesChanged bool
|
|
||||||
|
|
||||||
n *Notifier
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBatcher(batchTime time.Duration, n *Notifier) *batcher {
|
|
||||||
return &batcher{
|
|
||||||
tick: time.NewTicker(batchTime),
|
|
||||||
cancelCh: make(chan struct{}),
|
|
||||||
patches: make(map[types.NodeID]tailcfg.PeerChange),
|
|
||||||
n: n,
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *batcher) close() {
|
|
||||||
b.cancelCh <- struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// addOrPassthrough adds the update to the batcher, if it is not a
|
|
||||||
// type that is currently batched, it will be sent immediately.
|
|
||||||
func (b *batcher) addOrPassthrough(update types.StateUpdate) {
|
|
||||||
notifierBatcherWaitersForLock.WithLabelValues("lock", "add").Inc()
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
notifierBatcherWaitersForLock.WithLabelValues("lock", "add").Dec()
|
|
||||||
|
|
||||||
switch update.Type {
|
|
||||||
case types.StatePeerChanged:
|
|
||||||
b.changedNodeIDs.Add(update.ChangeNodes...)
|
|
||||||
b.nodesChanged = true
|
|
||||||
notifierBatcherChanges.WithLabelValues().Set(float64(b.changedNodeIDs.Len()))
|
|
||||||
|
|
||||||
case types.StatePeerChangedPatch:
|
|
||||||
for _, newPatch := range update.ChangePatches {
|
|
||||||
if curr, ok := b.patches[types.NodeID(newPatch.NodeID)]; ok {
|
|
||||||
overwritePatch(&curr, newPatch)
|
|
||||||
b.patches[types.NodeID(newPatch.NodeID)] = curr
|
|
||||||
} else {
|
|
||||||
b.patches[types.NodeID(newPatch.NodeID)] = *newPatch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.patchesChanged = true
|
|
||||||
notifierBatcherPatches.WithLabelValues().Set(float64(len(b.patches)))
|
|
||||||
|
|
||||||
default:
|
|
||||||
b.n.sendAll(update)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// flush sends all the accumulated patches to all
|
|
||||||
// nodes in the notifier.
|
|
||||||
func (b *batcher) flush() {
|
|
||||||
notifierBatcherWaitersForLock.WithLabelValues("lock", "flush").Inc()
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
notifierBatcherWaitersForLock.WithLabelValues("lock", "flush").Dec()
|
|
||||||
|
|
||||||
if b.nodesChanged || b.patchesChanged {
|
|
||||||
var patches []*tailcfg.PeerChange
|
|
||||||
// If a node is getting a full update from a change
|
|
||||||
// node update, then the patch can be dropped.
|
|
||||||
for nodeID, patch := range b.patches {
|
|
||||||
if b.changedNodeIDs.Contains(nodeID) {
|
|
||||||
delete(b.patches, nodeID)
|
|
||||||
} else {
|
|
||||||
patches = append(patches, &patch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
changedNodes := b.changedNodeIDs.Slice().AsSlice()
|
|
||||||
sort.Slice(changedNodes, func(i, j int) bool {
|
|
||||||
return changedNodes[i] < changedNodes[j]
|
|
||||||
})
|
|
||||||
|
|
||||||
if b.changedNodeIDs.Slice().Len() > 0 {
|
|
||||||
update := types.StateUpdate{
|
|
||||||
Type: types.StatePeerChanged,
|
|
||||||
ChangeNodes: changedNodes,
|
|
||||||
}
|
|
||||||
|
|
||||||
b.n.sendAll(update)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(patches) > 0 {
|
|
||||||
patchUpdate := types.StateUpdate{
|
|
||||||
Type: types.StatePeerChangedPatch,
|
|
||||||
ChangePatches: patches,
|
|
||||||
}
|
|
||||||
|
|
||||||
b.n.sendAll(patchUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.changedNodeIDs = set.Slice[types.NodeID]{}
|
|
||||||
notifierBatcherChanges.WithLabelValues().Set(0)
|
|
||||||
b.nodesChanged = false
|
|
||||||
b.patches = make(map[types.NodeID]tailcfg.PeerChange, len(b.patches))
|
|
||||||
notifierBatcherPatches.WithLabelValues().Set(0)
|
|
||||||
b.patchesChanged = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *batcher) doWork() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-b.cancelCh:
|
|
||||||
return
|
|
||||||
case <-b.tick.C:
|
|
||||||
b.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// overwritePatch takes the current patch and a newer patch
|
|
||||||
// and override any field that has changed
|
|
||||||
func overwritePatch(currPatch, newPatch *tailcfg.PeerChange) {
|
|
||||||
if newPatch.DERPRegion != 0 {
|
|
||||||
currPatch.DERPRegion = newPatch.DERPRegion
|
|
||||||
}
|
|
||||||
|
|
||||||
if newPatch.Cap != 0 {
|
|
||||||
currPatch.Cap = newPatch.Cap
|
|
||||||
}
|
|
||||||
|
|
||||||
if newPatch.CapMap != nil {
|
|
||||||
currPatch.CapMap = newPatch.CapMap
|
|
||||||
}
|
|
||||||
|
|
||||||
if newPatch.Endpoints != nil {
|
|
||||||
currPatch.Endpoints = newPatch.Endpoints
|
|
||||||
}
|
|
||||||
|
|
||||||
if newPatch.Key != nil {
|
|
||||||
currPatch.Key = newPatch.Key
|
|
||||||
}
|
|
||||||
|
|
||||||
if newPatch.KeySignature != nil {
|
|
||||||
currPatch.KeySignature = newPatch.KeySignature
|
|
||||||
}
|
|
||||||
|
|
||||||
if newPatch.DiscoKey != nil {
|
|
||||||
currPatch.DiscoKey = newPatch.DiscoKey
|
|
||||||
}
|
|
||||||
|
|
||||||
if newPatch.Online != nil {
|
|
||||||
currPatch.Online = newPatch.Online
|
|
||||||
}
|
|
||||||
|
|
||||||
if newPatch.LastSeen != nil {
|
|
||||||
currPatch.LastSeen = newPatch.LastSeen
|
|
||||||
}
|
|
||||||
|
|
||||||
if newPatch.KeyExpiry != nil {
|
|
||||||
currPatch.KeyExpiry = newPatch.KeyExpiry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,249 +0,0 @@
|
|||||||
package notifier
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/netip"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
"github.com/juanfont/headscale/hscontrol/types"
|
|
||||||
"github.com/juanfont/headscale/hscontrol/util"
|
|
||||||
"tailscale.com/tailcfg"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBatcher(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
updates []types.StateUpdate
|
|
||||||
want []types.StateUpdate
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "full-passthrough",
|
|
||||||
updates: []types.StateUpdate{
|
|
||||||
{
|
|
||||||
Type: types.StateFullUpdate,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []types.StateUpdate{
|
|
||||||
{
|
|
||||||
Type: types.StateFullUpdate,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "derp-passthrough",
|
|
||||||
updates: []types.StateUpdate{
|
|
||||||
{
|
|
||||||
Type: types.StateDERPUpdated,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []types.StateUpdate{
|
|
||||||
{
|
|
||||||
Type: types.StateDERPUpdated,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "single-node-update",
|
|
||||||
updates: []types.StateUpdate{
|
|
||||||
{
|
|
||||||
Type: types.StatePeerChanged,
|
|
||||||
ChangeNodes: []types.NodeID{
|
|
||||||
2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []types.StateUpdate{
|
|
||||||
{
|
|
||||||
Type: types.StatePeerChanged,
|
|
||||||
ChangeNodes: []types.NodeID{
|
|
||||||
2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "merge-node-update",
|
|
||||||
updates: []types.StateUpdate{
|
|
||||||
{
|
|
||||||
Type: types.StatePeerChanged,
|
|
||||||
ChangeNodes: []types.NodeID{
|
|
||||||
2, 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: types.StatePeerChanged,
|
|
||||||
ChangeNodes: []types.NodeID{
|
|
||||||
2, 3,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []types.StateUpdate{
|
|
||||||
{
|
|
||||||
Type: types.StatePeerChanged,
|
|
||||||
ChangeNodes: []types.NodeID{
|
|
||||||
2, 3, 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "single-patch-update",
|
|
||||||
updates: []types.StateUpdate{
|
|
||||||
{
|
|
||||||
Type: types.StatePeerChangedPatch,
|
|
||||||
ChangePatches: []*tailcfg.PeerChange{
|
|
||||||
{
|
|
||||||
NodeID: 2,
|
|
||||||
DERPRegion: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []types.StateUpdate{
|
|
||||||
{
|
|
||||||
Type: types.StatePeerChangedPatch,
|
|
||||||
ChangePatches: []*tailcfg.PeerChange{
|
|
||||||
{
|
|
||||||
NodeID: 2,
|
|
||||||
DERPRegion: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "merge-patch-to-same-node-update",
|
|
||||||
updates: []types.StateUpdate{
|
|
||||||
{
|
|
||||||
Type: types.StatePeerChangedPatch,
|
|
||||||
ChangePatches: []*tailcfg.PeerChange{
|
|
||||||
{
|
|
||||||
NodeID: 2,
|
|
||||||
DERPRegion: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: types.StatePeerChangedPatch,
|
|
||||||
ChangePatches: []*tailcfg.PeerChange{
|
|
||||||
{
|
|
||||||
NodeID: 2,
|
|
||||||
DERPRegion: 6,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []types.StateUpdate{
|
|
||||||
{
|
|
||||||
Type: types.StatePeerChangedPatch,
|
|
||||||
ChangePatches: []*tailcfg.PeerChange{
|
|
||||||
{
|
|
||||||
NodeID: 2,
|
|
||||||
DERPRegion: 6,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "merge-patch-to-multiple-node-update",
|
|
||||||
updates: []types.StateUpdate{
|
|
||||||
{
|
|
||||||
Type: types.StatePeerChangedPatch,
|
|
||||||
ChangePatches: []*tailcfg.PeerChange{
|
|
||||||
{
|
|
||||||
NodeID: 3,
|
|
||||||
Endpoints: []netip.AddrPort{
|
|
||||||
netip.MustParseAddrPort("1.1.1.1:9090"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: types.StatePeerChangedPatch,
|
|
||||||
ChangePatches: []*tailcfg.PeerChange{
|
|
||||||
{
|
|
||||||
NodeID: 3,
|
|
||||||
Endpoints: []netip.AddrPort{
|
|
||||||
netip.MustParseAddrPort("1.1.1.1:9090"),
|
|
||||||
netip.MustParseAddrPort("2.2.2.2:8080"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: types.StatePeerChangedPatch,
|
|
||||||
ChangePatches: []*tailcfg.PeerChange{
|
|
||||||
{
|
|
||||||
NodeID: 4,
|
|
||||||
DERPRegion: 6,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: types.StatePeerChangedPatch,
|
|
||||||
ChangePatches: []*tailcfg.PeerChange{
|
|
||||||
{
|
|
||||||
NodeID: 4,
|
|
||||||
Cap: tailcfg.CapabilityVersion(54),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []types.StateUpdate{
|
|
||||||
{
|
|
||||||
Type: types.StatePeerChangedPatch,
|
|
||||||
ChangePatches: []*tailcfg.PeerChange{
|
|
||||||
{
|
|
||||||
NodeID: 3,
|
|
||||||
Endpoints: []netip.AddrPort{
|
|
||||||
netip.MustParseAddrPort("1.1.1.1:9090"),
|
|
||||||
netip.MustParseAddrPort("2.2.2.2:8080"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
NodeID: 4,
|
|
||||||
DERPRegion: 6,
|
|
||||||
Cap: tailcfg.CapabilityVersion(54),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
n := NewNotifier(&types.Config{
|
|
||||||
Tuning: types.Tuning{
|
|
||||||
// We will call flush manually for the tests,
|
|
||||||
// so do not run the worker.
|
|
||||||
BatchChangeDelay: time.Hour,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
ch := make(chan types.StateUpdate, 30)
|
|
||||||
defer close(ch)
|
|
||||||
n.AddNode(1, ch)
|
|
||||||
defer n.RemoveNode(1, ch)
|
|
||||||
|
|
||||||
for _, u := range tt.updates {
|
|
||||||
n.NotifyAll(context.Background(), u)
|
|
||||||
}
|
|
||||||
|
|
||||||
n.b.flush()
|
|
||||||
|
|
||||||
var got []types.StateUpdate
|
|
||||||
for len(ch) > 0 {
|
|
||||||
out := <-ch
|
|
||||||
got = append(got, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
if diff := cmp.Diff(tt.want, got, util.Comparers...); diff != "" {
|
|
||||||
t.Errorf("batcher() unexpected result (-want +got):\n%s", diff)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@@ -36,38 +36,6 @@ const (
|
|||||||
expectedTokenItems = 2
|
expectedTokenItems = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
var theInternetSet *netipx.IPSet
|
|
||||||
|
|
||||||
// theInternet returns the IPSet for the Internet.
|
|
||||||
// https://www.youtube.com/watch?v=iDbyYGrswtg
|
|
||||||
func theInternet() *netipx.IPSet {
|
|
||||||
if theInternetSet != nil {
|
|
||||||
return theInternetSet
|
|
||||||
}
|
|
||||||
|
|
||||||
var internetBuilder netipx.IPSetBuilder
|
|
||||||
internetBuilder.AddPrefix(netip.MustParsePrefix("2000::/3"))
|
|
||||||
internetBuilder.AddPrefix(netip.MustParsePrefix("0.0.0.0/0"))
|
|
||||||
|
|
||||||
// Delete Private network addresses
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc1918
|
|
||||||
internetBuilder.RemovePrefix(netip.MustParsePrefix("fc00::/7"))
|
|
||||||
internetBuilder.RemovePrefix(netip.MustParsePrefix("10.0.0.0/8"))
|
|
||||||
internetBuilder.RemovePrefix(netip.MustParsePrefix("172.16.0.0/12"))
|
|
||||||
internetBuilder.RemovePrefix(netip.MustParsePrefix("192.168.0.0/16"))
|
|
||||||
|
|
||||||
// Delete Tailscale networks
|
|
||||||
internetBuilder.RemovePrefix(netip.MustParsePrefix("fd7a:115c:a1e0::/48"))
|
|
||||||
internetBuilder.RemovePrefix(netip.MustParsePrefix("100.64.0.0/10"))
|
|
||||||
|
|
||||||
// Delete "cant find DHCP networks"
|
|
||||||
internetBuilder.RemovePrefix(netip.MustParsePrefix("fe80::/10")) // link-loca
|
|
||||||
internetBuilder.RemovePrefix(netip.MustParsePrefix("169.254.0.0/16"))
|
|
||||||
|
|
||||||
theInternetSet, _ := internetBuilder.IPSet()
|
|
||||||
return theInternetSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// For some reason golang.org/x/net/internal/iana is an internal package.
|
// For some reason golang.org/x/net/internal/iana is an internal package.
|
||||||
const (
|
const (
|
||||||
protocolICMP = 1 // Internet Control Message
|
protocolICMP = 1 // Internet Control Message
|
||||||
@@ -253,28 +221,28 @@ func ReduceFilterRules(node *types.Node, rules []tailcfg.FilterRule) []tailcfg.F
|
|||||||
// record if the rule is actually relevant for the given node.
|
// record if the rule is actually relevant for the given node.
|
||||||
dests := []tailcfg.NetPortRange{}
|
dests := []tailcfg.NetPortRange{}
|
||||||
|
|
||||||
DEST_LOOP:
|
|
||||||
for _, dest := range rule.DstPorts {
|
for _, dest := range rule.DstPorts {
|
||||||
expanded, err := util.ParseIPSet(dest.IP, nil)
|
expanded, err := util.ParseIPSet(dest.IP, nil)
|
||||||
// Fail closed, if we cant parse it, then we should not allow
|
// Fail closed, if we cant parse it, then we should not allow
|
||||||
// access.
|
// access.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue DEST_LOOP
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.InIPSet(expanded) {
|
if node.InIPSet(expanded) {
|
||||||
dests = append(dests, dest)
|
dests = append(dests, dest)
|
||||||
continue DEST_LOOP
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the node exposes routes, ensure they are note removed
|
// If the node exposes routes, ensure they are note removed
|
||||||
// when the filters are reduced.
|
// when the filters are reduced.
|
||||||
if node.Hostinfo != nil {
|
if node.Hostinfo != nil {
|
||||||
|
// TODO(kradalby): Evaluate if we should only keep
|
||||||
|
// the routes if the route is enabled. This will
|
||||||
|
// require database access in this part of the code.
|
||||||
if len(node.Hostinfo.RoutableIPs) > 0 {
|
if len(node.Hostinfo.RoutableIPs) > 0 {
|
||||||
for _, routableIP := range node.Hostinfo.RoutableIPs {
|
for _, routableIP := range node.Hostinfo.RoutableIPs {
|
||||||
if expanded.OverlapsPrefix(routableIP) {
|
if expanded.ContainsPrefix(routableIP) {
|
||||||
dests = append(dests, dest)
|
dests = append(dests, dest)
|
||||||
continue DEST_LOOP
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -549,7 +517,6 @@ func (pol *ACLPolicy) expandSource(
|
|||||||
// - a host
|
// - a host
|
||||||
// - an ip
|
// - an ip
|
||||||
// - a cidr
|
// - a cidr
|
||||||
// - an autogroup
|
|
||||||
// and transform these in IPAddresses.
|
// and transform these in IPAddresses.
|
||||||
func (pol *ACLPolicy) ExpandAlias(
|
func (pol *ACLPolicy) ExpandAlias(
|
||||||
nodes types.Nodes,
|
nodes types.Nodes,
|
||||||
@@ -575,10 +542,6 @@ func (pol *ACLPolicy) ExpandAlias(
|
|||||||
return pol.expandIPsFromTag(alias, nodes)
|
return pol.expandIPsFromTag(alias, nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isAutoGroup(alias) {
|
|
||||||
return expandAutoGroup(alias)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if alias is a user
|
// if alias is a user
|
||||||
if ips, err := pol.expandIPsFromUser(alias, nodes); ips != nil {
|
if ips, err := pol.expandIPsFromUser(alias, nodes); ips != nil {
|
||||||
return ips, err
|
return ips, err
|
||||||
@@ -899,16 +862,6 @@ func (pol *ACLPolicy) expandIPsFromIPPrefix(
|
|||||||
return build.IPSet()
|
return build.IPSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
func expandAutoGroup(alias string) (*netipx.IPSet, error) {
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(alias, "autogroup:internet"):
|
|
||||||
return theInternet(), nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown autogroup %q", alias)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isWildcard(str string) bool {
|
func isWildcard(str string) bool {
|
||||||
return str == "*"
|
return str == "*"
|
||||||
}
|
}
|
||||||
@@ -921,10 +874,6 @@ func isTag(str string) bool {
|
|||||||
return strings.HasPrefix(str, "tag:")
|
return strings.HasPrefix(str, "tag:")
|
||||||
}
|
}
|
||||||
|
|
||||||
func isAutoGroup(str string) bool {
|
|
||||||
return strings.HasPrefix(str, "autogroup:")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TagsOfNode will return the tags of the current node.
|
// TagsOfNode will return the tags of the current node.
|
||||||
// Invalid tags are tags added by a user on a node, and that user doesn't have authority to add this tag.
|
// 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.
|
// Valid tags are tags added by a user that is allowed in the ACL policy to add this tag.
|
||||||
|
@@ -532,7 +532,7 @@ func (s *Suite) TestRuleInvalidGeneration(c *check.C) {
|
|||||||
"example-host-2:80"
|
"example-host-2:80"
|
||||||
],
|
],
|
||||||
"deny": [
|
"deny": [
|
||||||
"example-host-2:100"
|
"exapmle-host-2:100"
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -635,7 +635,7 @@ func Test_expandGroup(t *testing.T) {
|
|||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "InexistentGroup",
|
name: "InexistantGroup",
|
||||||
field: field{
|
field: field{
|
||||||
pol: ACLPolicy{
|
pol: ACLPolicy{
|
||||||
Groups: Groups{
|
Groups: Groups{
|
||||||
@@ -1765,108 +1765,6 @@ func TestACLPolicy_generateFilterRules(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tsExitNodeDest is the list of destination IP ranges that are allowed when
|
|
||||||
// you dump the filter list from a Tailscale node connected to Tailscale SaaS.
|
|
||||||
var tsExitNodeDest = []tailcfg.NetPortRange{
|
|
||||||
{
|
|
||||||
IP: "0.0.0.0-9.255.255.255",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "11.0.0.0-100.63.255.255",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "100.128.0.0-169.253.255.255",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "169.255.0.0-172.15.255.255",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "172.32.0.0-192.167.255.255",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "192.169.0.0-255.255.255.255",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "2000::-3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// hsExitNodeDest is the list of destination IP ranges that are allowed when
|
|
||||||
// we use headscale "autogroup:internet"
|
|
||||||
var hsExitNodeDest = []tailcfg.NetPortRange{
|
|
||||||
{IP: "0.0.0.0/5", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "8.0.0.0/7", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "11.0.0.0/8", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "12.0.0.0/6", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "16.0.0.0/4", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "32.0.0.0/3", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "64.0.0.0/3", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "96.0.0.0/6", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "100.0.0.0/10", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "100.128.0.0/9", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "101.0.0.0/8", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "102.0.0.0/7", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "104.0.0.0/5", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "112.0.0.0/4", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "128.0.0.0/3", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "160.0.0.0/5", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "168.0.0.0/8", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "169.0.0.0/9", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "169.128.0.0/10", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "169.192.0.0/11", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "169.224.0.0/12", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "169.240.0.0/13", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "169.248.0.0/14", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "169.252.0.0/15", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "169.255.0.0/16", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "170.0.0.0/7", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "172.0.0.0/12", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "172.32.0.0/11", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "172.64.0.0/10", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "172.128.0.0/9", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "173.0.0.0/8", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "174.0.0.0/7", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "176.0.0.0/4", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.0.0.0/9", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.128.0.0/11", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.160.0.0/13", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.169.0.0/16", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.170.0.0/15", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.172.0.0/14", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.176.0.0/12", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.192.0.0/10", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "193.0.0.0/8", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "194.0.0.0/7", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "196.0.0.0/6", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "200.0.0.0/5", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "208.0.0.0/4", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "224.0.0.0/3", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "2000::/3", Ports: tailcfg.PortRangeAny},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTheInternet(t *testing.T) {
|
|
||||||
internetSet := theInternet()
|
|
||||||
|
|
||||||
internetPrefs := internetSet.Prefixes()
|
|
||||||
|
|
||||||
for i, _ := range internetPrefs {
|
|
||||||
if internetPrefs[i].String() != hsExitNodeDest[i].IP {
|
|
||||||
t.Errorf("prefix from internet set %q != hsExit list %q", internetPrefs[i].String(), hsExitNodeDest[i].IP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(internetPrefs) != len(hsExitNodeDest) {
|
|
||||||
t.Fatalf("expected same length of prefixes, internet: %d, hsExit: %d", len(internetPrefs), len(hsExitNodeDest))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReduceFilterRules(t *testing.T) {
|
func TestReduceFilterRules(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -1971,473 +1869,15 @@ func TestReduceFilterRules(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "1786-reducing-breaks-exit-nodes-the-client",
|
|
||||||
pol: ACLPolicy{
|
|
||||||
Hosts: Hosts{
|
|
||||||
// Exit node
|
|
||||||
"internal": netip.MustParsePrefix("100.64.0.100/32"),
|
|
||||||
},
|
|
||||||
Groups: Groups{
|
|
||||||
"group:team": {"user3", "user2", "user1"},
|
|
||||||
},
|
|
||||||
ACLs: []ACL{
|
|
||||||
{
|
|
||||||
Action: "accept",
|
|
||||||
Sources: []string{"group:team"},
|
|
||||||
Destinations: []string{
|
|
||||||
"internal:*",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: "accept",
|
|
||||||
Sources: []string{"group:team"},
|
|
||||||
Destinations: []string{
|
|
||||||
"autogroup:internet:*",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
node: &types.Node{
|
|
||||||
IPv4: iap("100.64.0.1"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::1"),
|
|
||||||
User: types.User{Name: "user1"},
|
|
||||||
},
|
|
||||||
peers: types.Nodes{
|
|
||||||
&types.Node{
|
|
||||||
IPv4: iap("100.64.0.2"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::2"),
|
|
||||||
User: types.User{Name: "user2"},
|
|
||||||
},
|
|
||||||
// "internal" exit node
|
|
||||||
&types.Node{
|
|
||||||
IPv4: iap("100.64.0.100"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::100"),
|
|
||||||
User: types.User{Name: "user100"},
|
|
||||||
Hostinfo: &tailcfg.Hostinfo{
|
|
||||||
RoutableIPs: []netip.Prefix{types.ExitRouteV4, types.ExitRouteV6},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []tailcfg.FilterRule{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "1786-reducing-breaks-exit-nodes-the-exit",
|
|
||||||
pol: ACLPolicy{
|
|
||||||
Hosts: Hosts{
|
|
||||||
// Exit node
|
|
||||||
"internal": netip.MustParsePrefix("100.64.0.100/32"),
|
|
||||||
},
|
|
||||||
Groups: Groups{
|
|
||||||
"group:team": {"user3", "user2", "user1"},
|
|
||||||
},
|
|
||||||
ACLs: []ACL{
|
|
||||||
{
|
|
||||||
Action: "accept",
|
|
||||||
Sources: []string{"group:team"},
|
|
||||||
Destinations: []string{
|
|
||||||
"internal:*",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: "accept",
|
|
||||||
Sources: []string{"group:team"},
|
|
||||||
Destinations: []string{
|
|
||||||
"autogroup:internet:*",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
node: &types.Node{
|
|
||||||
IPv4: iap("100.64.0.100"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::100"),
|
|
||||||
User: types.User{Name: "user100"},
|
|
||||||
Hostinfo: &tailcfg.Hostinfo{
|
|
||||||
RoutableIPs: []netip.Prefix{types.ExitRouteV4, types.ExitRouteV6},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
peers: types.Nodes{
|
|
||||||
&types.Node{
|
|
||||||
IPv4: iap("100.64.0.2"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::2"),
|
|
||||||
User: types.User{Name: "user2"},
|
|
||||||
},
|
|
||||||
&types.Node{
|
|
||||||
IPv4: iap("100.64.0.1"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::1"),
|
|
||||||
User: types.User{Name: "user1"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []tailcfg.FilterRule{
|
|
||||||
{
|
|
||||||
SrcIPs: []string{"100.64.0.1/32", "100.64.0.2/32", "fd7a:115c:a1e0::1/128", "fd7a:115c:a1e0::2/128"},
|
|
||||||
DstPorts: []tailcfg.NetPortRange{
|
|
||||||
{
|
|
||||||
IP: "100.64.0.100/32",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "fd7a:115c:a1e0::100/128",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
SrcIPs: []string{"100.64.0.1/32", "100.64.0.2/32", "fd7a:115c:a1e0::1/128", "fd7a:115c:a1e0::2/128"},
|
|
||||||
DstPorts: hsExitNodeDest,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "1786-reducing-breaks-exit-nodes-the-example-from-issue",
|
|
||||||
pol: ACLPolicy{
|
|
||||||
Hosts: Hosts{
|
|
||||||
// Exit node
|
|
||||||
"internal": netip.MustParsePrefix("100.64.0.100/32"),
|
|
||||||
},
|
|
||||||
Groups: Groups{
|
|
||||||
"group:team": {"user3", "user2", "user1"},
|
|
||||||
},
|
|
||||||
ACLs: []ACL{
|
|
||||||
{
|
|
||||||
Action: "accept",
|
|
||||||
Sources: []string{"group:team"},
|
|
||||||
Destinations: []string{
|
|
||||||
"internal:*",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: "accept",
|
|
||||||
Sources: []string{"group:team"},
|
|
||||||
Destinations: []string{
|
|
||||||
"0.0.0.0/5:*",
|
|
||||||
"8.0.0.0/7:*",
|
|
||||||
"11.0.0.0/8:*",
|
|
||||||
"12.0.0.0/6:*",
|
|
||||||
"16.0.0.0/4:*",
|
|
||||||
"32.0.0.0/3:*",
|
|
||||||
"64.0.0.0/2:*",
|
|
||||||
"128.0.0.0/3:*",
|
|
||||||
"160.0.0.0/5:*",
|
|
||||||
"168.0.0.0/6:*",
|
|
||||||
"172.0.0.0/12:*",
|
|
||||||
"172.32.0.0/11:*",
|
|
||||||
"172.64.0.0/10:*",
|
|
||||||
"172.128.0.0/9:*",
|
|
||||||
"173.0.0.0/8:*",
|
|
||||||
"174.0.0.0/7:*",
|
|
||||||
"176.0.0.0/4:*",
|
|
||||||
"192.0.0.0/9:*",
|
|
||||||
"192.128.0.0/11:*",
|
|
||||||
"192.160.0.0/13:*",
|
|
||||||
"192.169.0.0/16:*",
|
|
||||||
"192.170.0.0/15:*",
|
|
||||||
"192.172.0.0/14:*",
|
|
||||||
"192.176.0.0/12:*",
|
|
||||||
"192.192.0.0/10:*",
|
|
||||||
"193.0.0.0/8:*",
|
|
||||||
"194.0.0.0/7:*",
|
|
||||||
"196.0.0.0/6:*",
|
|
||||||
"200.0.0.0/5:*",
|
|
||||||
"208.0.0.0/4:*",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
node: &types.Node{
|
|
||||||
IPv4: iap("100.64.0.100"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::100"),
|
|
||||||
User: types.User{Name: "user100"},
|
|
||||||
Hostinfo: &tailcfg.Hostinfo{
|
|
||||||
RoutableIPs: []netip.Prefix{types.ExitRouteV4, types.ExitRouteV6},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
peers: types.Nodes{
|
|
||||||
&types.Node{
|
|
||||||
IPv4: iap("100.64.0.2"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::2"),
|
|
||||||
User: types.User{Name: "user2"},
|
|
||||||
},
|
|
||||||
&types.Node{
|
|
||||||
IPv4: iap("100.64.0.1"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::1"),
|
|
||||||
User: types.User{Name: "user1"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []tailcfg.FilterRule{
|
|
||||||
{
|
|
||||||
SrcIPs: []string{"100.64.0.1/32", "100.64.0.2/32", "fd7a:115c:a1e0::1/128", "fd7a:115c:a1e0::2/128"},
|
|
||||||
DstPorts: []tailcfg.NetPortRange{
|
|
||||||
{
|
|
||||||
IP: "100.64.0.100/32",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "fd7a:115c:a1e0::100/128",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
SrcIPs: []string{"100.64.0.1/32", "100.64.0.2/32", "fd7a:115c:a1e0::1/128", "fd7a:115c:a1e0::2/128"},
|
|
||||||
DstPorts: []tailcfg.NetPortRange{
|
|
||||||
{IP: "0.0.0.0/5", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "8.0.0.0/7", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "11.0.0.0/8", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "12.0.0.0/6", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "16.0.0.0/4", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "32.0.0.0/3", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "64.0.0.0/2", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "fd7a:115c:a1e0::1/128", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "fd7a:115c:a1e0::2/128", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "fd7a:115c:a1e0::100/128", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "128.0.0.0/3", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "160.0.0.0/5", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "168.0.0.0/6", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "172.0.0.0/12", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "172.32.0.0/11", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "172.64.0.0/10", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "172.128.0.0/9", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "173.0.0.0/8", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "174.0.0.0/7", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "176.0.0.0/4", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.0.0.0/9", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.128.0.0/11", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.160.0.0/13", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.169.0.0/16", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.170.0.0/15", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.172.0.0/14", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.176.0.0/12", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "192.192.0.0/10", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "193.0.0.0/8", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "194.0.0.0/7", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "196.0.0.0/6", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "200.0.0.0/5", Ports: tailcfg.PortRangeAny},
|
|
||||||
{IP: "208.0.0.0/4", Ports: tailcfg.PortRangeAny},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "1786-reducing-breaks-exit-nodes-app-connector-like",
|
|
||||||
pol: ACLPolicy{
|
|
||||||
Hosts: Hosts{
|
|
||||||
// Exit node
|
|
||||||
"internal": netip.MustParsePrefix("100.64.0.100/32"),
|
|
||||||
},
|
|
||||||
Groups: Groups{
|
|
||||||
"group:team": {"user3", "user2", "user1"},
|
|
||||||
},
|
|
||||||
ACLs: []ACL{
|
|
||||||
{
|
|
||||||
Action: "accept",
|
|
||||||
Sources: []string{"group:team"},
|
|
||||||
Destinations: []string{
|
|
||||||
"internal:*",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: "accept",
|
|
||||||
Sources: []string{"group:team"},
|
|
||||||
Destinations: []string{
|
|
||||||
"8.0.0.0/8:*",
|
|
||||||
"16.0.0.0/8:*",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
node: &types.Node{
|
|
||||||
IPv4: iap("100.64.0.100"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::100"),
|
|
||||||
User: types.User{Name: "user100"},
|
|
||||||
Hostinfo: &tailcfg.Hostinfo{
|
|
||||||
RoutableIPs: []netip.Prefix{netip.MustParsePrefix("8.0.0.0/16"), netip.MustParsePrefix("16.0.0.0/16")},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
peers: types.Nodes{
|
|
||||||
&types.Node{
|
|
||||||
IPv4: iap("100.64.0.2"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::2"),
|
|
||||||
User: types.User{Name: "user2"},
|
|
||||||
},
|
|
||||||
&types.Node{
|
|
||||||
IPv4: iap("100.64.0.1"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::1"),
|
|
||||||
User: types.User{Name: "user1"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []tailcfg.FilterRule{
|
|
||||||
{
|
|
||||||
SrcIPs: []string{"100.64.0.1/32", "100.64.0.2/32", "fd7a:115c:a1e0::1/128", "fd7a:115c:a1e0::2/128"},
|
|
||||||
DstPorts: []tailcfg.NetPortRange{
|
|
||||||
{
|
|
||||||
IP: "100.64.0.100/32",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "fd7a:115c:a1e0::100/128",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
SrcIPs: []string{"100.64.0.1/32", "100.64.0.2/32", "fd7a:115c:a1e0::1/128", "fd7a:115c:a1e0::2/128"},
|
|
||||||
DstPorts: []tailcfg.NetPortRange{
|
|
||||||
{
|
|
||||||
IP: "8.0.0.0/8",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "16.0.0.0/8",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "1786-reducing-breaks-exit-nodes-app-connector-like2",
|
|
||||||
pol: ACLPolicy{
|
|
||||||
Hosts: Hosts{
|
|
||||||
// Exit node
|
|
||||||
"internal": netip.MustParsePrefix("100.64.0.100/32"),
|
|
||||||
},
|
|
||||||
Groups: Groups{
|
|
||||||
"group:team": {"user3", "user2", "user1"},
|
|
||||||
},
|
|
||||||
ACLs: []ACL{
|
|
||||||
{
|
|
||||||
Action: "accept",
|
|
||||||
Sources: []string{"group:team"},
|
|
||||||
Destinations: []string{
|
|
||||||
"internal:*",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: "accept",
|
|
||||||
Sources: []string{"group:team"},
|
|
||||||
Destinations: []string{
|
|
||||||
"8.0.0.0/16:*",
|
|
||||||
"16.0.0.0/16:*",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
node: &types.Node{
|
|
||||||
IPv4: iap("100.64.0.100"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::100"),
|
|
||||||
User: types.User{Name: "user100"},
|
|
||||||
Hostinfo: &tailcfg.Hostinfo{
|
|
||||||
RoutableIPs: []netip.Prefix{netip.MustParsePrefix("8.0.0.0/8"), netip.MustParsePrefix("16.0.0.0/8")},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
peers: types.Nodes{
|
|
||||||
&types.Node{
|
|
||||||
IPv4: iap("100.64.0.2"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::2"),
|
|
||||||
User: types.User{Name: "user2"},
|
|
||||||
},
|
|
||||||
&types.Node{
|
|
||||||
IPv4: iap("100.64.0.1"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::1"),
|
|
||||||
User: types.User{Name: "user1"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []tailcfg.FilterRule{
|
|
||||||
{
|
|
||||||
SrcIPs: []string{"100.64.0.1/32", "100.64.0.2/32", "fd7a:115c:a1e0::1/128", "fd7a:115c:a1e0::2/128"},
|
|
||||||
DstPorts: []tailcfg.NetPortRange{
|
|
||||||
{
|
|
||||||
IP: "100.64.0.100/32",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "fd7a:115c:a1e0::100/128",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
SrcIPs: []string{"100.64.0.1/32", "100.64.0.2/32", "fd7a:115c:a1e0::1/128", "fd7a:115c:a1e0::2/128"},
|
|
||||||
DstPorts: []tailcfg.NetPortRange{
|
|
||||||
{
|
|
||||||
IP: "8.0.0.0/16",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "16.0.0.0/16",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "1817-reduce-breaks-32-mask",
|
|
||||||
pol: ACLPolicy{
|
|
||||||
Hosts: Hosts{
|
|
||||||
"vlan1": netip.MustParsePrefix("172.16.0.0/24"),
|
|
||||||
"dns1": netip.MustParsePrefix("172.16.0.21/32"),
|
|
||||||
},
|
|
||||||
Groups: Groups{
|
|
||||||
"group:access": {"user1"},
|
|
||||||
},
|
|
||||||
ACLs: []ACL{
|
|
||||||
{
|
|
||||||
Action: "accept",
|
|
||||||
Sources: []string{"group:access"},
|
|
||||||
Destinations: []string{
|
|
||||||
"tag:access-servers:*",
|
|
||||||
"dns1:*",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
node: &types.Node{
|
|
||||||
IPv4: iap("100.64.0.100"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::100"),
|
|
||||||
User: types.User{Name: "user100"},
|
|
||||||
Hostinfo: &tailcfg.Hostinfo{
|
|
||||||
RoutableIPs: []netip.Prefix{netip.MustParsePrefix("172.16.0.0/24")},
|
|
||||||
},
|
|
||||||
ForcedTags: types.StringList{"tag:access-servers"},
|
|
||||||
},
|
|
||||||
peers: types.Nodes{
|
|
||||||
&types.Node{
|
|
||||||
IPv4: iap("100.64.0.1"),
|
|
||||||
IPv6: iap("fd7a:115c:a1e0::1"),
|
|
||||||
User: types.User{Name: "user1"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []tailcfg.FilterRule{
|
|
||||||
{
|
|
||||||
SrcIPs: []string{"100.64.0.1/32", "fd7a:115c:a1e0::1/128"},
|
|
||||||
DstPorts: []tailcfg.NetPortRange{
|
|
||||||
{
|
|
||||||
IP: "100.64.0.100/32",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "fd7a:115c:a1e0::100/128",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "172.16.0.21/32",
|
|
||||||
Ports: tailcfg.PortRangeAny,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, _ := tt.pol.CompileFilterRules(
|
rules, _ := tt.pol.CompileFilterRules(
|
||||||
append(tt.peers, tt.node),
|
append(tt.peers, tt.node),
|
||||||
)
|
)
|
||||||
|
|
||||||
got = ReduceFilterRules(tt.node, got)
|
got := ReduceFilterRules(tt.node, rules)
|
||||||
|
|
||||||
if diff := cmp.Diff(tt.want, got); diff != "" {
|
if diff := cmp.Diff(tt.want, got); diff != "" {
|
||||||
log.Trace().Interface("got", got).Msg("result")
|
log.Trace().Interface("got", got).Msg("result")
|
||||||
@@ -2604,7 +2044,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "all hosts can talk to each other",
|
name: "all hosts can talk to each other",
|
||||||
args: args{
|
args: args{
|
||||||
nodes: types.Nodes{ // list of all nodes in the database
|
nodes: types.Nodes{ // list of all nodess in the database
|
||||||
&types.Node{
|
&types.Node{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
IPv4: iap("100.64.0.1"),
|
IPv4: iap("100.64.0.1"),
|
||||||
@@ -2651,7 +2091,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "One host can talk to another, but not all hosts",
|
name: "One host can talk to another, but not all hosts",
|
||||||
args: args{
|
args: args{
|
||||||
nodes: types.Nodes{ // list of all nodes in the database
|
nodes: types.Nodes{ // list of all nodess in the database
|
||||||
&types.Node{
|
&types.Node{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
IPv4: iap("100.64.0.1"),
|
IPv4: iap("100.64.0.1"),
|
||||||
@@ -2693,7 +2133,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "host cannot directly talk to destination, but return path is authorized",
|
name: "host cannot directly talk to destination, but return path is authorized",
|
||||||
args: args{
|
args: args{
|
||||||
nodes: types.Nodes{ // list of all nodes in the database
|
nodes: types.Nodes{ // list of all nodess in the database
|
||||||
&types.Node{
|
&types.Node{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
IPv4: iap("100.64.0.1"),
|
IPv4: iap("100.64.0.1"),
|
||||||
@@ -2735,7 +2175,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "rules allows all hosts to reach one destination",
|
name: "rules allows all hosts to reach one destination",
|
||||||
args: args{
|
args: args{
|
||||||
nodes: types.Nodes{ // list of all nodes in the database
|
nodes: types.Nodes{ // list of all nodess in the database
|
||||||
&types.Node{
|
&types.Node{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
IPv4: iap("100.64.0.1"),
|
IPv4: iap("100.64.0.1"),
|
||||||
@@ -2777,7 +2217,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "rules allows all hosts to reach one destination, destination can reach all hosts",
|
name: "rules allows all hosts to reach one destination, destination can reach all hosts",
|
||||||
args: args{
|
args: args{
|
||||||
nodes: types.Nodes{ // list of all nodes in the database
|
nodes: types.Nodes{ // list of all nodess in the database
|
||||||
&types.Node{
|
&types.Node{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
IPv4: iap("100.64.0.1"),
|
IPv4: iap("100.64.0.1"),
|
||||||
@@ -2824,7 +2264,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "rule allows all hosts to reach all destinations",
|
name: "rule allows all hosts to reach all destinations",
|
||||||
args: args{
|
args: args{
|
||||||
nodes: types.Nodes{ // list of all nodes in the database
|
nodes: types.Nodes{ // list of all nodess in the database
|
||||||
&types.Node{
|
&types.Node{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
IPv4: iap("100.64.0.1"),
|
IPv4: iap("100.64.0.1"),
|
||||||
@@ -2871,7 +2311,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "without rule all communications are forbidden",
|
name: "without rule all communications are forbidden",
|
||||||
args: args{
|
args: args{
|
||||||
nodes: types.Nodes{ // list of all nodes in the database
|
nodes: types.Nodes{ // list of all nodess in the database
|
||||||
&types.Node{
|
&types.Node{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
IPv4: iap("100.64.0.1"),
|
IPv4: iap("100.64.0.1"),
|
||||||
|
@@ -9,13 +9,13 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/juanfont/headscale/hscontrol/db"
|
"github.com/juanfont/headscale/hscontrol/db"
|
||||||
"github.com/juanfont/headscale/hscontrol/mapper"
|
"github.com/juanfont/headscale/hscontrol/mapper"
|
||||||
"github.com/juanfont/headscale/hscontrol/types"
|
"github.com/juanfont/headscale/hscontrol/types"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/sasha-s/go-deadlock"
|
|
||||||
xslices "golang.org/x/exp/slices"
|
xslices "golang.org/x/exp/slices"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
@@ -29,6 +29,11 @@ type contextKey string
|
|||||||
|
|
||||||
const nodeNameContextKey = contextKey("nodeName")
|
const nodeNameContextKey = contextKey("nodeName")
|
||||||
|
|
||||||
|
type sessionManager struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
sess map[types.NodeID]*mapSession
|
||||||
|
}
|
||||||
|
|
||||||
type mapSession struct {
|
type mapSession struct {
|
||||||
h *Headscale
|
h *Headscale
|
||||||
req tailcfg.MapRequest
|
req tailcfg.MapRequest
|
||||||
@@ -36,13 +41,12 @@ type mapSession struct {
|
|||||||
capVer tailcfg.CapabilityVersion
|
capVer tailcfg.CapabilityVersion
|
||||||
mapper *mapper.Mapper
|
mapper *mapper.Mapper
|
||||||
|
|
||||||
cancelChMu deadlock.Mutex
|
serving bool
|
||||||
|
servingMu sync.Mutex
|
||||||
|
|
||||||
ch chan types.StateUpdate
|
ch chan types.StateUpdate
|
||||||
cancelCh chan struct{}
|
cancelCh chan struct{}
|
||||||
cancelChOpen bool
|
|
||||||
|
|
||||||
keepAlive time.Duration
|
|
||||||
keepAliveTicker *time.Ticker
|
keepAliveTicker *time.Ticker
|
||||||
|
|
||||||
node *types.Node
|
node *types.Node
|
||||||
@@ -62,18 +66,10 @@ func (h *Headscale) newMapSession(
|
|||||||
) *mapSession {
|
) *mapSession {
|
||||||
warnf, infof, tracef, errf := logPollFunc(req, node)
|
warnf, infof, tracef, errf := logPollFunc(req, node)
|
||||||
|
|
||||||
var updateChan chan types.StateUpdate
|
// Use a buffered channel in case a node is not fully ready
|
||||||
if req.Stream {
|
// to receive a message to make sure we dont block the entire
|
||||||
// Use a buffered channel in case a node is not fully ready
|
// notifier.
|
||||||
// to receive a message to make sure we dont block the entire
|
updateChan := make(chan types.StateUpdate, h.cfg.Tuning.NodeMapSessionBufferedChanSize)
|
||||||
// notifier.
|
|
||||||
updateChan = make(chan types.StateUpdate, h.cfg.Tuning.NodeMapSessionBufferedChanSize)
|
|
||||||
updateChan <- types.StateUpdate{
|
|
||||||
Type: types.StateFullUpdate,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ka := keepAliveInterval + (time.Duration(rand.IntN(9000)) * time.Millisecond)
|
|
||||||
|
|
||||||
return &mapSession{
|
return &mapSession{
|
||||||
h: h,
|
h: h,
|
||||||
@@ -84,12 +80,13 @@ func (h *Headscale) newMapSession(
|
|||||||
capVer: req.Version,
|
capVer: req.Version,
|
||||||
mapper: h.mapper,
|
mapper: h.mapper,
|
||||||
|
|
||||||
ch: updateChan,
|
// serving indicates if a client is being served.
|
||||||
cancelCh: make(chan struct{}),
|
serving: false,
|
||||||
cancelChOpen: true,
|
|
||||||
|
|
||||||
keepAlive: ka,
|
ch: updateChan,
|
||||||
keepAliveTicker: nil,
|
cancelCh: make(chan struct{}),
|
||||||
|
|
||||||
|
keepAliveTicker: time.NewTicker(keepAliveInterval + (time.Duration(rand.IntN(9000)) * time.Millisecond)),
|
||||||
|
|
||||||
// Loggers
|
// Loggers
|
||||||
warnf: warnf,
|
warnf: warnf,
|
||||||
@@ -100,23 +97,15 @@ func (h *Headscale) newMapSession(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *mapSession) close() {
|
func (m *mapSession) close() {
|
||||||
m.cancelChMu.Lock()
|
m.servingMu.Lock()
|
||||||
defer m.cancelChMu.Unlock()
|
defer m.servingMu.Unlock()
|
||||||
|
if !m.serving {
|
||||||
if !m.cancelChOpen {
|
|
||||||
mapResponseClosed.WithLabelValues("chanclosed").Inc()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m.tracef("mapSession (%p) sending message on cancel chan", m)
|
m.tracef("mapSession (%p) sending message on cancel chan")
|
||||||
select {
|
m.cancelCh <- struct{}{}
|
||||||
case m.cancelCh <- struct{}{}:
|
m.tracef("mapSession (%p) sent message on cancel chan")
|
||||||
mapResponseClosed.WithLabelValues("sent").Inc()
|
|
||||||
m.tracef("mapSession (%p) sent message on cancel chan", m)
|
|
||||||
case <-time.After(30 * time.Second):
|
|
||||||
mapResponseClosed.WithLabelValues("timeout").Inc()
|
|
||||||
m.tracef("mapSession (%p) timed out sending close message", m)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mapSession) isStreaming() bool {
|
func (m *mapSession) isStreaming() bool {
|
||||||
@@ -131,12 +120,40 @@ func (m *mapSession) isReadOnlyUpdate() bool {
|
|||||||
return !m.req.Stream && m.req.OmitPeers && m.req.ReadOnly
|
return !m.req.Stream && m.req.OmitPeers && m.req.ReadOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mapSession) resetKeepAlive() {
|
// handlePoll ensures the node gets the appropriate updates from either
|
||||||
m.keepAliveTicker.Reset(m.keepAlive)
|
// polling or immediate responses.
|
||||||
}
|
//
|
||||||
|
//nolint:gocyclo
|
||||||
// serve handles non-streaming requests.
|
|
||||||
func (m *mapSession) serve() {
|
func (m *mapSession) serve() {
|
||||||
|
// Register with the notifier if this is a streaming
|
||||||
|
// session
|
||||||
|
if m.isStreaming() {
|
||||||
|
// defers are called in reverse order,
|
||||||
|
// so top one is executed last.
|
||||||
|
|
||||||
|
// Failover the node's routes if any.
|
||||||
|
defer m.infof("node has disconnected, mapSession: %p", m)
|
||||||
|
defer m.pollFailoverRoutes("node closing connection", m.node)
|
||||||
|
|
||||||
|
defer m.h.updateNodeOnlineStatus(false, m.node)
|
||||||
|
defer m.h.nodeNotifier.RemoveNode(m.node.ID)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
m.servingMu.Lock()
|
||||||
|
defer m.servingMu.Unlock()
|
||||||
|
|
||||||
|
m.serving = false
|
||||||
|
close(m.cancelCh)
|
||||||
|
}()
|
||||||
|
|
||||||
|
m.serving = true
|
||||||
|
|
||||||
|
m.h.nodeNotifier.AddNode(m.node.ID, m.ch)
|
||||||
|
m.h.updateNodeOnlineStatus(true, m.node)
|
||||||
|
|
||||||
|
m.infof("node has connected, mapSession: %p", m)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(kradalby): A set todos to harden:
|
// TODO(kradalby): A set todos to harden:
|
||||||
// - func to tell the stream to die, readonly -> false, !stream && omitpeers -> false, true
|
// - func to tell the stream to die, readonly -> false, !stream && omitpeers -> false, true
|
||||||
|
|
||||||
@@ -173,43 +190,13 @@ func (m *mapSession) serve() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// serveLongPoll ensures the node gets the appropriate updates from either
|
|
||||||
// polling or immediate responses.
|
|
||||||
//
|
|
||||||
//nolint:gocyclo
|
|
||||||
func (m *mapSession) serveLongPoll() {
|
|
||||||
// Clean up the session when the client disconnects
|
|
||||||
defer func() {
|
|
||||||
m.cancelChMu.Lock()
|
|
||||||
m.cancelChOpen = false
|
|
||||||
close(m.cancelCh)
|
|
||||||
m.cancelChMu.Unlock()
|
|
||||||
|
|
||||||
// only update node status if the node channel was removed.
|
|
||||||
// in principal, it will be removed, but the client rapidly
|
|
||||||
// reconnects, the channel might be of another connection.
|
|
||||||
// In that case, it is not closed and the node is still online.
|
|
||||||
if m.h.nodeNotifier.RemoveNode(m.node.ID, m.ch) {
|
|
||||||
// Failover the node's routes if any.
|
|
||||||
m.h.updateNodeOnlineStatus(false, m.node)
|
|
||||||
m.pollFailoverRoutes("node closing connection", m.node)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.infof("node has disconnected, mapSession: %p, chan: %p", m, m.ch)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// From version 68, all streaming requests can be treated as read only.
|
// From version 68, all streaming requests can be treated as read only.
|
||||||
// TODO: Remove when we drop support for 1.48
|
|
||||||
if m.capVer < 68 {
|
if m.capVer < 68 {
|
||||||
// Error has been handled/written to client in the func
|
// Error has been handled/written to client in the func
|
||||||
// return
|
// return
|
||||||
err := m.handleSaveNode()
|
err := m.handleSaveNode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mapResponseWriteUpdatesInStream.WithLabelValues("error").Inc()
|
mapResponseWriteUpdatesInStream.WithLabelValues("error").Inc()
|
||||||
|
|
||||||
m.close()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mapResponseWriteUpdatesInStream.WithLabelValues("ok").Inc()
|
mapResponseWriteUpdatesInStream.WithLabelValues("ok").Inc()
|
||||||
@@ -231,41 +218,33 @@ func (m *mapSession) serveLongPoll() {
|
|||||||
ctx, cancel := context.WithCancel(context.WithValue(m.ctx, nodeNameContextKey, m.node.Hostname))
|
ctx, cancel := context.WithCancel(context.WithValue(m.ctx, nodeNameContextKey, m.node.Hostname))
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
m.keepAliveTicker = time.NewTicker(m.keepAlive)
|
// TODO(kradalby): Make this available through a tuning envvar
|
||||||
|
wait := time.Second
|
||||||
|
|
||||||
m.h.nodeNotifier.AddNode(m.node.ID, m.ch)
|
// Add a circuit breaker, if the loop is not interrupted
|
||||||
go m.h.updateNodeOnlineStatus(true, m.node)
|
// inbetween listening for the channels, some updates
|
||||||
|
// might get stale and stucked in the "changed" map
|
||||||
|
// defined below.
|
||||||
|
blockBreaker := time.NewTicker(wait)
|
||||||
|
|
||||||
m.infof("node has connected, mapSession: %p, chan: %p", m, m.ch)
|
// true means changed, false means removed
|
||||||
|
var changed map[types.NodeID]bool
|
||||||
|
var patches []*tailcfg.PeerChange
|
||||||
|
var derp bool
|
||||||
|
|
||||||
|
// Set full to true to immediatly send a full mapresponse
|
||||||
|
full := true
|
||||||
|
prev := time.Now()
|
||||||
|
lastMessage := ""
|
||||||
|
|
||||||
// Loop through updates and continuously send them to the
|
// Loop through updates and continuously send them to the
|
||||||
// client.
|
// client.
|
||||||
for {
|
for {
|
||||||
// consume channels with update, keep alives or "batch" blocking signals
|
// If a full update has been requested or there are patches, then send it immediately
|
||||||
select {
|
// otherwise wait for the "batching" of changes or patches
|
||||||
case <-m.cancelCh:
|
if full || patches != nil || (changed != nil && time.Since(prev) > wait) {
|
||||||
m.tracef("poll cancelled received")
|
|
||||||
mapResponseEnded.WithLabelValues("cancelled").Inc()
|
|
||||||
return
|
|
||||||
|
|
||||||
case <-ctx.Done():
|
|
||||||
m.tracef("poll context done")
|
|
||||||
mapResponseEnded.WithLabelValues("done").Inc()
|
|
||||||
return
|
|
||||||
|
|
||||||
// Consume updates sent to node
|
|
||||||
case update, ok := <-m.ch:
|
|
||||||
if !ok {
|
|
||||||
m.tracef("update channel closed, streaming session is likely being replaced")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m.tracef("received stream update: %s %s", update.Type.String(), update.Message)
|
|
||||||
mapResponseUpdateReceived.WithLabelValues(update.Type.String()).Inc()
|
|
||||||
|
|
||||||
var data []byte
|
var data []byte
|
||||||
var err error
|
var err error
|
||||||
var lastMessage string
|
|
||||||
|
|
||||||
// Ensure the node object is updated, for example, there
|
// Ensure the node object is updated, for example, there
|
||||||
// might have been a hostinfo update in a sidechannel
|
// might have been a hostinfo update in a sidechannel
|
||||||
@@ -277,43 +256,62 @@ func (m *mapSession) serveLongPoll() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there are patches _and_ fully changed nodes, filter the
|
||||||
|
// patches and remove all patches that are present for the full
|
||||||
|
// changes updates. This allows us to send them as part of the
|
||||||
|
// PeerChange update, but only for nodes that are not fully changed.
|
||||||
|
// The fully changed nodes will be updated from the database and
|
||||||
|
// have all the updates needed.
|
||||||
|
// This means that the patches left are for nodes that has no
|
||||||
|
// updates that requires a full update.
|
||||||
|
// Patches are not suppose to be mixed in, but can be.
|
||||||
|
//
|
||||||
|
// From tailcfg docs:
|
||||||
|
// These are applied after Peers* above, but in practice the
|
||||||
|
// control server should only send these on their own, without
|
||||||
|
//
|
||||||
|
// Currently, there is no effort to merge patch updates, they
|
||||||
|
// are all sent, and the client will apply them in order.
|
||||||
|
// TODO(kradalby): Merge Patches for the same IDs to send less
|
||||||
|
// data and give the client less work.
|
||||||
|
if patches != nil && changed != nil {
|
||||||
|
var filteredPatches []*tailcfg.PeerChange
|
||||||
|
|
||||||
|
for _, patch := range patches {
|
||||||
|
if _, ok := changed[types.NodeID(patch.NodeID)]; !ok {
|
||||||
|
filteredPatches = append(filteredPatches, patch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
patches = filteredPatches
|
||||||
|
}
|
||||||
|
|
||||||
updateType := "full"
|
updateType := "full"
|
||||||
switch update.Type {
|
// When deciding what update to send, the following is considered,
|
||||||
case types.StateFullUpdate:
|
// Full is a superset of all updates, when a full update is requested,
|
||||||
|
// send only that and move on, all other updates will be present in
|
||||||
|
// a full map response.
|
||||||
|
//
|
||||||
|
// If a map of changed nodes exists, prefer sending that as it will
|
||||||
|
// contain all the updates for the node, including patches, as it
|
||||||
|
// is fetched freshly from the database when building the response.
|
||||||
|
//
|
||||||
|
// If there is full changes registered, but we have patches for individual
|
||||||
|
// nodes, send them.
|
||||||
|
//
|
||||||
|
// Finally, if a DERP map is the only request, send that alone.
|
||||||
|
if full {
|
||||||
m.tracef("Sending Full MapResponse")
|
m.tracef("Sending Full MapResponse")
|
||||||
data, err = m.mapper.FullMapResponse(m.req, m.node, m.h.ACLPolicy, fmt.Sprintf("from mapSession: %p, stream: %t", m, m.isStreaming()))
|
data, err = m.mapper.FullMapResponse(m.req, m.node, m.h.ACLPolicy, fmt.Sprintf("from mapSession: %p, stream: %t", m, m.isStreaming()))
|
||||||
case types.StatePeerChanged:
|
} else if changed != nil {
|
||||||
changed := make(map[types.NodeID]bool, len(update.ChangeNodes))
|
|
||||||
|
|
||||||
for _, nodeID := range update.ChangeNodes {
|
|
||||||
changed[nodeID] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
lastMessage = update.Message
|
|
||||||
m.tracef(fmt.Sprintf("Sending Changed MapResponse: %v", lastMessage))
|
m.tracef(fmt.Sprintf("Sending Changed MapResponse: %v", lastMessage))
|
||||||
data, err = m.mapper.PeerChangedResponse(m.req, m.node, changed, update.ChangePatches, m.h.ACLPolicy, lastMessage)
|
data, err = m.mapper.PeerChangedResponse(m.req, m.node, changed, patches, m.h.ACLPolicy, lastMessage)
|
||||||
updateType = "change"
|
updateType = "change"
|
||||||
|
} else if patches != nil {
|
||||||
case types.StatePeerChangedPatch:
|
|
||||||
m.tracef(fmt.Sprintf("Sending Changed Patch MapResponse: %v", lastMessage))
|
m.tracef(fmt.Sprintf("Sending Changed Patch MapResponse: %v", lastMessage))
|
||||||
data, err = m.mapper.PeerChangedPatchResponse(m.req, m.node, update.ChangePatches, m.h.ACLPolicy)
|
data, err = m.mapper.PeerChangedPatchResponse(m.req, m.node, patches, m.h.ACLPolicy)
|
||||||
updateType = "patch"
|
updateType = "patch"
|
||||||
case types.StatePeerRemoved:
|
} else if derp {
|
||||||
changed := make(map[types.NodeID]bool, len(update.Removed))
|
|
||||||
|
|
||||||
for _, nodeID := range update.Removed {
|
|
||||||
changed[nodeID] = false
|
|
||||||
}
|
|
||||||
m.tracef(fmt.Sprintf("Sending Changed MapResponse: %v", lastMessage))
|
|
||||||
data, err = m.mapper.PeerChangedResponse(m.req, m.node, changed, update.ChangePatches, m.h.ACLPolicy, lastMessage)
|
|
||||||
updateType = "remove"
|
|
||||||
case types.StateSelfUpdate:
|
|
||||||
lastMessage = update.Message
|
|
||||||
m.tracef(fmt.Sprintf("Sending Changed MapResponse: %v", lastMessage))
|
|
||||||
// create the map so an empty (self) update is sent
|
|
||||||
data, err = m.mapper.PeerChangedResponse(m.req, m.node, make(map[types.NodeID]bool), update.ChangePatches, m.h.ACLPolicy, lastMessage)
|
|
||||||
updateType = "remove"
|
|
||||||
case types.StateDERPUpdated:
|
|
||||||
m.tracef("Sending DERPUpdate MapResponse")
|
m.tracef("Sending DERPUpdate MapResponse")
|
||||||
data, err = m.mapper.DERPMapResponse(m.req, m.node, m.h.DERPMap)
|
data, err = m.mapper.DERPMapResponse(m.req, m.node, m.h.DERPMap)
|
||||||
updateType = "derp"
|
updateType = "derp"
|
||||||
@@ -325,13 +323,15 @@ func (m *mapSession) serveLongPoll() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// log.Trace().Str("node", m.node.Hostname).TimeDiff("timeSpent", time.Now(), startMapResp).Str("mkey", m.node.MachineKey.String()).Int("type", int(update.Type)).Msg("finished making map response")
|
||||||
|
|
||||||
// Only send update if there is change
|
// Only send update if there is change
|
||||||
if data != nil {
|
if data != nil {
|
||||||
startWrite := time.Now()
|
startWrite := time.Now()
|
||||||
_, err = m.w.Write(data)
|
_, err = m.w.Write(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mapResponseSent.WithLabelValues("error", updateType).Inc()
|
mapResponseSent.WithLabelValues("error", updateType).Inc()
|
||||||
m.errf(err, "could not write the map response(%s), for mapSession: %p", update.Type.String(), m)
|
m.errf(err, "Could not write the map response, for mapSession: %p", m)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,12 +344,70 @@ func (m *mapSession) serveLongPoll() {
|
|||||||
|
|
||||||
log.Trace().Str("node", m.node.Hostname).TimeDiff("timeSpent", time.Now(), startWrite).Str("mkey", m.node.MachineKey.String()).Msg("finished writing mapresp to node")
|
log.Trace().Str("node", m.node.Hostname).TimeDiff("timeSpent", time.Now(), startWrite).Str("mkey", m.node.MachineKey.String()).Msg("finished writing mapresp to node")
|
||||||
|
|
||||||
if debugHighCardinalityMetrics {
|
|
||||||
mapResponseLastSentSeconds.WithLabelValues(updateType, m.node.ID.String()).Set(float64(time.Now().Unix()))
|
|
||||||
}
|
|
||||||
mapResponseSent.WithLabelValues("ok", updateType).Inc()
|
mapResponseSent.WithLabelValues("ok", updateType).Inc()
|
||||||
m.tracef("update sent")
|
m.tracef("update sent")
|
||||||
m.resetKeepAlive()
|
}
|
||||||
|
|
||||||
|
// reset
|
||||||
|
changed = nil
|
||||||
|
patches = nil
|
||||||
|
lastMessage = ""
|
||||||
|
full = false
|
||||||
|
derp = false
|
||||||
|
prev = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume channels with update, keep alives or "batch" blocking signals
|
||||||
|
select {
|
||||||
|
case <-m.cancelCh:
|
||||||
|
m.tracef("poll cancelled received")
|
||||||
|
return
|
||||||
|
case <-ctx.Done():
|
||||||
|
m.tracef("poll context done")
|
||||||
|
return
|
||||||
|
|
||||||
|
// Avoid infinite block that would potentially leave
|
||||||
|
// some updates in the changed map.
|
||||||
|
case <-blockBreaker.C:
|
||||||
|
continue
|
||||||
|
|
||||||
|
// Consume all updates sent to node
|
||||||
|
case update := <-m.ch:
|
||||||
|
m.tracef("received stream update: %s %s", update.Type.String(), update.Message)
|
||||||
|
mapResponseUpdateReceived.WithLabelValues(update.Type.String()).Inc()
|
||||||
|
|
||||||
|
switch update.Type {
|
||||||
|
case types.StateFullUpdate:
|
||||||
|
full = true
|
||||||
|
case types.StatePeerChanged:
|
||||||
|
if changed == nil {
|
||||||
|
changed = make(map[types.NodeID]bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, nodeID := range update.ChangeNodes {
|
||||||
|
changed[nodeID] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
lastMessage = update.Message
|
||||||
|
case types.StatePeerChangedPatch:
|
||||||
|
patches = append(patches, update.ChangePatches...)
|
||||||
|
case types.StatePeerRemoved:
|
||||||
|
if changed == nil {
|
||||||
|
changed = make(map[types.NodeID]bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, nodeID := range update.Removed {
|
||||||
|
changed[nodeID] = false
|
||||||
|
}
|
||||||
|
case types.StateSelfUpdate:
|
||||||
|
// create the map so an empty (self) update is sent
|
||||||
|
if changed == nil {
|
||||||
|
changed = make(map[types.NodeID]bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
lastMessage = update.Message
|
||||||
|
case types.StateDERPUpdated:
|
||||||
|
derp = true
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-m.keepAliveTicker.C:
|
case <-m.keepAliveTicker.C:
|
||||||
@@ -372,9 +430,6 @@ func (m *mapSession) serveLongPoll() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if debugHighCardinalityMetrics {
|
|
||||||
mapResponseLastSentSeconds.WithLabelValues("keepalive", m.node.ID.String()).Set(float64(time.Now().Unix()))
|
|
||||||
}
|
|
||||||
mapResponseSent.WithLabelValues("ok", "keepalive").Inc()
|
mapResponseSent.WithLabelValues("ok", "keepalive").Inc()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,6 +486,16 @@ func (h *Headscale) updateNodeOnlineStatus(online bool, node *types.Node) {
|
|||||||
}, node.ID)
|
}, node.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func closeChanWithLog[C chan []byte | chan struct{} | chan types.StateUpdate](channel C, node, name string) {
|
||||||
|
log.Trace().
|
||||||
|
Str("handler", "PollNetMap").
|
||||||
|
Str("node", node).
|
||||||
|
Str("channel", "Done").
|
||||||
|
Msg(fmt.Sprintf("Closing %s channel", name))
|
||||||
|
|
||||||
|
close(channel)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mapSession) handleEndpointUpdate() {
|
func (m *mapSession) handleEndpointUpdate() {
|
||||||
m.tracef("received endpoint update")
|
m.tracef("received endpoint update")
|
||||||
|
|
||||||
@@ -442,17 +507,6 @@ func (m *mapSession) handleEndpointUpdate() {
|
|||||||
m.node.ApplyPeerChange(&change)
|
m.node.ApplyPeerChange(&change)
|
||||||
|
|
||||||
sendUpdate, routesChanged := hostInfoChanged(m.node.Hostinfo, m.req.Hostinfo)
|
sendUpdate, routesChanged := hostInfoChanged(m.node.Hostinfo, m.req.Hostinfo)
|
||||||
|
|
||||||
// The node might not set NetInfo if it has not changed and if
|
|
||||||
// the full HostInfo object is overrwritten, the information is lost.
|
|
||||||
// If there is no NetInfo, keep the previous one.
|
|
||||||
// From 1.66 the client only sends it if changed:
|
|
||||||
// https://github.com/tailscale/tailscale/commit/e1011f138737286ecf5123ff887a7a5800d129a2
|
|
||||||
// TODO(kradalby): evaulate if we need better comparing of hostinfo
|
|
||||||
// before we take the changes.
|
|
||||||
if m.req.Hostinfo.NetInfo == nil {
|
|
||||||
m.req.Hostinfo.NetInfo = m.node.Hostinfo.NetInfo
|
|
||||||
}
|
|
||||||
m.node.Hostinfo = m.req.Hostinfo
|
m.node.Hostinfo = m.req.Hostinfo
|
||||||
|
|
||||||
logTracePeerChange(m.node.Hostname, sendUpdate, &change)
|
logTracePeerChange(m.node.Hostname, sendUpdate, &change)
|
||||||
|
@@ -10,7 +10,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/util/ctxkey"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -184,14 +183,10 @@ func StateUpdateExpire(nodeID NodeID, expiry time.Time) StateUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
NotifyOriginKey = ctxkey.New("notify.origin", "")
|
|
||||||
NotifyHostnameKey = ctxkey.New("notify.hostname", "")
|
|
||||||
)
|
|
||||||
|
|
||||||
func NotifyCtx(ctx context.Context, origin, hostname string) context.Context {
|
func NotifyCtx(ctx context.Context, origin, hostname string) context.Context {
|
||||||
ctx2, _ := context.WithTimeout(ctx, 3*time.Second)
|
ctx2, _ := context.WithTimeout(
|
||||||
ctx2 = NotifyOriginKey.WithValue(ctx2, origin)
|
context.WithValue(context.WithValue(ctx, "hostname", hostname), "origin", origin),
|
||||||
ctx2 = NotifyHostnameKey.WithValue(ctx2, hostname)
|
3*time.Second,
|
||||||
|
)
|
||||||
return ctx2
|
return ctx2
|
||||||
}
|
}
|
||||||
|
@@ -171,7 +171,6 @@ type LogConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Tuning struct {
|
type Tuning struct {
|
||||||
NotifierSendTimeout time.Duration
|
|
||||||
BatchChangeDelay time.Duration
|
BatchChangeDelay time.Duration
|
||||||
NodeMapSessionBufferedChanSize int
|
NodeMapSessionBufferedChanSize int
|
||||||
}
|
}
|
||||||
@@ -233,7 +232,6 @@ func LoadConfig(path string, isFile bool) error {
|
|||||||
|
|
||||||
viper.SetDefault("ephemeral_node_inactivity_timeout", "120s")
|
viper.SetDefault("ephemeral_node_inactivity_timeout", "120s")
|
||||||
|
|
||||||
viper.SetDefault("tuning.notifier_send_timeout", "800ms")
|
|
||||||
viper.SetDefault("tuning.batch_change_delay", "800ms")
|
viper.SetDefault("tuning.batch_change_delay", "800ms")
|
||||||
viper.SetDefault("tuning.node_mapsession_buffered_chan_size", 30)
|
viper.SetDefault("tuning.node_mapsession_buffered_chan_size", 30)
|
||||||
|
|
||||||
@@ -642,9 +640,6 @@ func GetHeadscaleConfig() (*Config, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
logConfig := GetLogConfig()
|
|
||||||
zerolog.SetGlobalLevel(logConfig.Level)
|
|
||||||
|
|
||||||
prefix4, err := PrefixV4()
|
prefix4, err := PrefixV4()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -655,10 +650,6 @@ func GetHeadscaleConfig() (*Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if prefix4 == nil && prefix6 == nil {
|
|
||||||
return nil, fmt.Errorf("no IPv4 or IPv6 prefix configured, minimum one prefix is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
allocStr := viper.GetString("prefixes.allocation")
|
allocStr := viper.GetString("prefixes.allocation")
|
||||||
var alloc IPAllocationStrategy
|
var alloc IPAllocationStrategy
|
||||||
switch allocStr {
|
switch allocStr {
|
||||||
@@ -667,12 +658,12 @@ func GetHeadscaleConfig() (*Config, error) {
|
|||||||
case string(IPAllocationStrategyRandom):
|
case string(IPAllocationStrategyRandom):
|
||||||
alloc = IPAllocationStrategyRandom
|
alloc = IPAllocationStrategyRandom
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("config error, prefixes.allocation is set to %s, which is not a valid strategy, allowed options: %s, %s", allocStr, IPAllocationStrategySequential, IPAllocationStrategyRandom)
|
log.Fatal().Msgf("config error, prefixes.allocation is set to %s, which is not a valid strategy, allowed options: %s, %s", allocStr, IPAllocationStrategySequential, IPAllocationStrategyRandom)
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsConfig, baseDomain := GetDNSConfig()
|
dnsConfig, baseDomain := GetDNSConfig()
|
||||||
derpConfig := GetDERPConfig()
|
derpConfig := GetDERPConfig()
|
||||||
logTailConfig := GetLogTailConfig()
|
logConfig := GetLogTailConfig()
|
||||||
randomizeClientPort := viper.GetBool("randomize_client_port")
|
randomizeClientPort := viper.GetBool("randomize_client_port")
|
||||||
|
|
||||||
oidcClientSecret := viper.GetString("oidc.client_secret")
|
oidcClientSecret := viper.GetString("oidc.client_secret")
|
||||||
@@ -754,7 +745,7 @@ func GetHeadscaleConfig() (*Config, error) {
|
|||||||
UseExpiryFromToken: viper.GetBool("oidc.use_expiry_from_token"),
|
UseExpiryFromToken: viper.GetBool("oidc.use_expiry_from_token"),
|
||||||
},
|
},
|
||||||
|
|
||||||
LogTail: logTailConfig,
|
LogTail: logConfig,
|
||||||
RandomizeClientPort: randomizeClientPort,
|
RandomizeClientPort: randomizeClientPort,
|
||||||
|
|
||||||
ACL: GetACLConfig(),
|
ACL: GetACLConfig(),
|
||||||
@@ -766,11 +757,10 @@ func GetHeadscaleConfig() (*Config, error) {
|
|||||||
Insecure: viper.GetBool("cli.insecure"),
|
Insecure: viper.GetBool("cli.insecure"),
|
||||||
},
|
},
|
||||||
|
|
||||||
Log: logConfig,
|
Log: GetLogConfig(),
|
||||||
|
|
||||||
// TODO(kradalby): Document these settings when more stable
|
// TODO(kradalby): Document these settings when more stable
|
||||||
Tuning: Tuning{
|
Tuning: Tuning{
|
||||||
NotifierSendTimeout: viper.GetDuration("tuning.notifier_send_timeout"),
|
|
||||||
BatchChangeDelay: viper.GetDuration("tuning.batch_change_delay"),
|
BatchChangeDelay: viper.GetDuration("tuning.batch_change_delay"),
|
||||||
NodeMapSessionBufferedChanSize: viper.GetInt("tuning.node_mapsession_buffered_chan_size"),
|
NodeMapSessionBufferedChanSize: viper.GetInt("tuning.node_mapsession_buffered_chan_size"),
|
||||||
},
|
},
|
||||||
|
@@ -43,10 +43,6 @@ func (id NodeID) Uint64() uint64 {
|
|||||||
return uint64(id)
|
return uint64(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (id NodeID) String() string {
|
|
||||||
return strconv.FormatUint(id.Uint64(), util.Base10)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node is a Headscale client.
|
// Node is a Headscale client.
|
||||||
type Node struct {
|
type Node struct {
|
||||||
ID NodeID `gorm:"primary_key"`
|
ID NodeID `gorm:"primary_key"`
|
||||||
@@ -112,20 +108,20 @@ type Node struct {
|
|||||||
// parts of headscale.
|
// parts of headscale.
|
||||||
GivenName string `gorm:"type:varchar(63);unique_index"`
|
GivenName string `gorm:"type:varchar(63);unique_index"`
|
||||||
UserID uint
|
UserID uint
|
||||||
User User `gorm:"constraint:OnDelete:CASCADE;"`
|
User User `gorm:"foreignKey:UserID"`
|
||||||
|
|
||||||
RegisterMethod string
|
RegisterMethod string
|
||||||
|
|
||||||
ForcedTags StringList
|
ForcedTags StringList
|
||||||
|
|
||||||
// TODO(kradalby): This seems like irrelevant information?
|
// TODO(kradalby): This seems like irrelevant information?
|
||||||
AuthKeyID *uint `sql:"DEFAULT:NULL"`
|
AuthKeyID uint
|
||||||
AuthKey *PreAuthKey `gorm:"constraint:OnDelete:SET NULL;"`
|
AuthKey *PreAuthKey
|
||||||
|
|
||||||
LastSeen *time.Time
|
LastSeen *time.Time
|
||||||
Expiry *time.Time
|
Expiry *time.Time
|
||||||
|
|
||||||
Routes []Route `gorm:"constraint:OnDelete:CASCADE;"`
|
Routes []Route
|
||||||
|
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
@@ -310,15 +306,11 @@ func (node *Node) AfterFind(tx *gorm.DB) error {
|
|||||||
}
|
}
|
||||||
node.NodeKey = nodeKey
|
node.NodeKey = nodeKey
|
||||||
|
|
||||||
// DiscoKey might be empty if a node has not sent it to headscale.
|
var discoKey key.DiscoPublic
|
||||||
// This means that this might fail if the disco key is empty.
|
if err := discoKey.UnmarshalText([]byte(node.DiscoKeyDatabaseField)); err != nil {
|
||||||
if node.DiscoKeyDatabaseField != "" {
|
return fmt.Errorf("unmarshalling disco key from db: %w", err)
|
||||||
var discoKey key.DiscoPublic
|
|
||||||
if err := discoKey.UnmarshalText([]byte(node.DiscoKeyDatabaseField)); err != nil {
|
|
||||||
return fmt.Errorf("unmarshalling disco key from db: %w", err)
|
|
||||||
}
|
|
||||||
node.DiscoKey = discoKey
|
|
||||||
}
|
}
|
||||||
|
node.DiscoKey = discoKey
|
||||||
|
|
||||||
endpoints := make([]netip.AddrPort, len(node.EndpointsDatabaseField))
|
endpoints := make([]netip.AddrPort, len(node.EndpointsDatabaseField))
|
||||||
for idx, ep := range node.EndpointsDatabaseField {
|
for idx, ep := range node.EndpointsDatabaseField {
|
||||||
|
@@ -14,11 +14,11 @@ type PreAuthKey struct {
|
|||||||
ID uint64 `gorm:"primary_key"`
|
ID uint64 `gorm:"primary_key"`
|
||||||
Key string
|
Key string
|
||||||
UserID uint
|
UserID uint
|
||||||
User User `gorm:"constraint:OnDelete:CASCADE;"`
|
User User
|
||||||
Reusable bool
|
Reusable bool
|
||||||
Ephemeral bool `gorm:"default:false"`
|
Ephemeral bool `gorm:"default:false"`
|
||||||
Used bool `gorm:"default:false"`
|
Used bool `gorm:"default:false"`
|
||||||
ACLTags []PreAuthKeyACLTag `gorm:"constraint:OnDelete:CASCADE;"`
|
ACLTags []PreAuthKeyACLTag
|
||||||
|
|
||||||
CreatedAt *time.Time
|
CreatedAt *time.Time
|
||||||
Expiration *time.Time
|
Expiration *time.Time
|
||||||
|
@@ -388,101 +388,6 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
|
|||||||
assert.Len(t, listedPreAuthKeys, 3)
|
assert.Len(t, listedPreAuthKeys, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
|
|
||||||
IntegrationSkip(t)
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
user1 := "user1"
|
|
||||||
user2 := "user2"
|
|
||||||
|
|
||||||
scenario, err := NewScenario(dockertestMaxWait())
|
|
||||||
assertNoErr(t, err)
|
|
||||||
defer scenario.Shutdown()
|
|
||||||
|
|
||||||
spec := map[string]int{
|
|
||||||
user1: 1,
|
|
||||||
user2: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipak"))
|
|
||||||
assertNoErr(t, err)
|
|
||||||
|
|
||||||
headscale, err := scenario.Headscale()
|
|
||||||
assertNoErr(t, err)
|
|
||||||
|
|
||||||
var user2Key v1.PreAuthKey
|
|
||||||
|
|
||||||
err = executeAndUnmarshal(
|
|
||||||
headscale,
|
|
||||||
[]string{
|
|
||||||
"headscale",
|
|
||||||
"preauthkeys",
|
|
||||||
"--user",
|
|
||||||
user2,
|
|
||||||
"create",
|
|
||||||
"--reusable",
|
|
||||||
"--expiration",
|
|
||||||
"24h",
|
|
||||||
"--output",
|
|
||||||
"json",
|
|
||||||
"--tags",
|
|
||||||
"tag:test1,tag:test2",
|
|
||||||
},
|
|
||||||
&user2Key,
|
|
||||||
)
|
|
||||||
assertNoErr(t, err)
|
|
||||||
|
|
||||||
allClients, err := scenario.ListTailscaleClients()
|
|
||||||
assertNoErrListClients(t, err)
|
|
||||||
|
|
||||||
assert.Len(t, allClients, 1)
|
|
||||||
|
|
||||||
client := allClients[0]
|
|
||||||
|
|
||||||
// Log out from user1
|
|
||||||
err = client.Logout()
|
|
||||||
assertNoErr(t, err)
|
|
||||||
|
|
||||||
err = scenario.WaitForTailscaleLogout()
|
|
||||||
assertNoErr(t, err)
|
|
||||||
|
|
||||||
status, err := client.Status()
|
|
||||||
assertNoErr(t, err)
|
|
||||||
if status.BackendState == "Starting" || status.BackendState == "Running" {
|
|
||||||
t.Fatalf("expected node to be logged out, backend state: %s", status.BackendState)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = client.Login(headscale.GetEndpoint(), user2Key.GetKey())
|
|
||||||
assertNoErr(t, err)
|
|
||||||
|
|
||||||
status, err = client.Status()
|
|
||||||
assertNoErr(t, err)
|
|
||||||
if status.BackendState != "Running" {
|
|
||||||
t.Fatalf("expected node to be logged in, backend state: %s", status.BackendState)
|
|
||||||
}
|
|
||||||
|
|
||||||
if status.Self.UserID.String() != "userid:2" {
|
|
||||||
t.Fatalf("expected node to be logged in as userid:2, got: %s", status.Self.UserID.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var listNodes []v1.Node
|
|
||||||
err = executeAndUnmarshal(
|
|
||||||
headscale,
|
|
||||||
[]string{
|
|
||||||
"headscale",
|
|
||||||
"nodes",
|
|
||||||
"list",
|
|
||||||
"--output",
|
|
||||||
"json",
|
|
||||||
},
|
|
||||||
&listNodes,
|
|
||||||
)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Len(t, listNodes, 1)
|
|
||||||
|
|
||||||
assert.Equal(t, "user2", listNodes[0].User.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApiKeyCommand(t *testing.T) {
|
func TestApiKeyCommand(t *testing.T) {
|
||||||
IntegrationSkip(t)
|
IntegrationSkip(t)
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -16,7 +15,6 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
"tailscale.com/client/tailscale/apitype"
|
"tailscale.com/client/tailscale/apitype"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
)
|
)
|
||||||
@@ -337,14 +335,14 @@ func TestTaildrop(t *testing.T) {
|
|||||||
IntegrationSkip(t)
|
IntegrationSkip(t)
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
retry := func(times int, sleepInterval time.Duration, doWork func() error) error {
|
retry := func(times int, sleepInverval time.Duration, doWork func() error) error {
|
||||||
var err error
|
var err error
|
||||||
for attempts := 0; attempts < times; attempts++ {
|
for attempts := 0; attempts < times; attempts++ {
|
||||||
err = doWork()
|
err = doWork()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
time.Sleep(sleepInterval)
|
time.Sleep(sleepInverval)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -795,7 +793,7 @@ func TestNodeOnlineStatus(t *testing.T) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// All peers of this nodes are reporting to be
|
// All peers of this nodess are reporting to be
|
||||||
// connected to the control server
|
// connected to the control server
|
||||||
assert.Truef(
|
assert.Truef(
|
||||||
t,
|
t,
|
||||||
@@ -831,10 +829,24 @@ func TestPingAllByIPManyUpDown(t *testing.T) {
|
|||||||
"user2": len(MustTestVersions),
|
"user2": len(MustTestVersions),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
headscaleConfig := map[string]string{
|
||||||
|
"HEADSCALE_DERP_URLS": "",
|
||||||
|
"HEADSCALE_DERP_SERVER_ENABLED": "true",
|
||||||
|
"HEADSCALE_DERP_SERVER_REGION_ID": "999",
|
||||||
|
"HEADSCALE_DERP_SERVER_REGION_CODE": "headscale",
|
||||||
|
"HEADSCALE_DERP_SERVER_REGION_NAME": "Headscale Embedded DERP",
|
||||||
|
"HEADSCALE_DERP_SERVER_STUN_LISTEN_ADDR": "0.0.0.0:3478",
|
||||||
|
"HEADSCALE_DERP_SERVER_PRIVATE_KEY_PATH": "/tmp/derp.key",
|
||||||
|
|
||||||
|
// Envknob for enabling DERP debug logs
|
||||||
|
"DERP_DEBUG_LOGS": "true",
|
||||||
|
"DERP_PROBER_DEBUG_LOGS": "true",
|
||||||
|
}
|
||||||
|
|
||||||
err = scenario.CreateHeadscaleEnv(spec,
|
err = scenario.CreateHeadscaleEnv(spec,
|
||||||
[]tsic.Option{},
|
[]tsic.Option{},
|
||||||
hsic.WithTestName("pingallbyipmany"),
|
hsic.WithTestName("pingallbyip"),
|
||||||
hsic.WithEmbeddedDERPServerOnly(),
|
hsic.WithConfigEnv(headscaleConfig),
|
||||||
hsic.WithTLS(),
|
hsic.WithTLS(),
|
||||||
hsic.WithHostnameAsServerURL(),
|
hsic.WithHostnameAsServerURL(),
|
||||||
)
|
)
|
||||||
@@ -858,35 +870,19 @@ func TestPingAllByIPManyUpDown(t *testing.T) {
|
|||||||
success := pingAllHelper(t, allClients, allAddrs)
|
success := pingAllHelper(t, allClients, allAddrs)
|
||||||
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))
|
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))
|
||||||
|
|
||||||
wg, _ := errgroup.WithContext(context.Background())
|
|
||||||
|
|
||||||
for run := range 3 {
|
for run := range 3 {
|
||||||
t.Logf("Starting DownUpPing run %d", run+1)
|
t.Logf("Starting DownUpPing run %d", run+1)
|
||||||
|
|
||||||
for _, client := range allClients {
|
for _, client := range allClients {
|
||||||
c := client
|
t.Logf("taking down %q", client.Hostname())
|
||||||
wg.Go(func() error {
|
client.Down()
|
||||||
t.Logf("taking down %q", c.Hostname())
|
|
||||||
return c.Down()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := wg.Wait(); err != nil {
|
|
||||||
t.Fatalf("failed to take down all nodes: %s", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
for _, client := range allClients {
|
for _, client := range allClients {
|
||||||
c := client
|
t.Logf("bringing up %q", client.Hostname())
|
||||||
wg.Go(func() error {
|
client.Up()
|
||||||
t.Logf("bringing up %q", c.Hostname())
|
|
||||||
return c.Up()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := wg.Wait(); err != nil {
|
|
||||||
t.Fatalf("failed to take down all nodes: %s", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
@@ -11,7 +11,6 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
@@ -286,13 +285,9 @@ func New(
|
|||||||
}
|
}
|
||||||
|
|
||||||
env := []string{
|
env := []string{
|
||||||
"HEADSCALE_DEBUG_PROFILING_ENABLED=1",
|
"HEADSCALE_PROFILING_ENABLED=1",
|
||||||
"HEADSCALE_DEBUG_PROFILING_PATH=/tmp/profile",
|
"HEADSCALE_PROFILING_PATH=/tmp/profile",
|
||||||
"HEADSCALE_DEBUG_DUMP_MAPRESPONSE_PATH=/tmp/mapresponses",
|
"HEADSCALE_DEBUG_DUMP_MAPRESPONSE_PATH=/tmp/mapresponses",
|
||||||
"HEADSCALE_DEBUG_DEADLOCK=1",
|
|
||||||
"HEADSCALE_DEBUG_DEADLOCK_TIMEOUT=5s",
|
|
||||||
"HEADSCALE_DEBUG_HIGH_CARDINALITY_METRICS=1",
|
|
||||||
"HEADSCALE_DEBUG_DUMP_CONFIG=1",
|
|
||||||
}
|
}
|
||||||
for key, value := range hsic.env {
|
for key, value := range hsic.env {
|
||||||
env = append(env, fmt.Sprintf("%s=%s", key, value))
|
env = append(env, fmt.Sprintf("%s=%s", key, value))
|
||||||
@@ -401,14 +396,6 @@ func (t *HeadscaleInContainer) Shutdown() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = t.SaveMetrics(fmt.Sprintf("/tmp/control/%s_metrics.txt", t.hostname))
|
|
||||||
if err != nil {
|
|
||||||
log.Printf(
|
|
||||||
"Failed to metrics from control: %s",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a interrupt signal to the "headscale" process inside the container
|
// Send a interrupt signal to the "headscale" process inside the container
|
||||||
// allowing it to shut down gracefully and flush the profile to disk.
|
// allowing it to shut down gracefully and flush the profile to disk.
|
||||||
// The container will live for a bit longer due to the sleep at the end.
|
// The container will live for a bit longer due to the sleep at the end.
|
||||||
@@ -461,25 +448,6 @@ func (t *HeadscaleInContainer) SaveLog(path string) error {
|
|||||||
return dockertestutil.SaveLog(t.pool, t.container, path)
|
return dockertestutil.SaveLog(t.pool, t.container, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HeadscaleInContainer) SaveMetrics(savePath string) error {
|
|
||||||
resp, err := http.Get(fmt.Sprintf("http://%s:9090/metrics", t.hostname))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("getting metrics: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
out, err := os.Create(savePath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating file for metrics: %w", err)
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
_, err = io.Copy(out, resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("copy response to file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *HeadscaleInContainer) SaveProfile(savePath string) error {
|
func (t *HeadscaleInContainer) SaveProfile(savePath string) error {
|
||||||
tarFile, err := t.FetchPath("/tmp/profile")
|
tarFile, err := t.FetchPath("/tmp/profile")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -751,7 +719,7 @@ func createCertificate(hostname string) ([]byte, []byte, error) {
|
|||||||
Locality: []string{"Leiden"},
|
Locality: []string{"Leiden"},
|
||||||
},
|
},
|
||||||
NotBefore: time.Now(),
|
NotBefore: time.Now(),
|
||||||
NotAfter: time.Now().Add(60 * time.Hour),
|
NotAfter: time.Now().Add(60 * time.Minute),
|
||||||
IsCA: true,
|
IsCA: true,
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{
|
ExtKeyUsage: []x509.ExtKeyUsage{
|
||||||
x509.ExtKeyUsageClientAuth,
|
x509.ExtKeyUsageClientAuth,
|
||||||
|
@@ -252,7 +252,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
|
|||||||
|
|
||||||
scenario, err := NewScenario(dockertestMaxWait())
|
scenario, err := NewScenario(dockertestMaxWait())
|
||||||
assertNoErrf(t, "failed to create scenario: %s", err)
|
assertNoErrf(t, "failed to create scenario: %s", err)
|
||||||
defer scenario.Shutdown()
|
// defer scenario.Shutdown()
|
||||||
|
|
||||||
spec := map[string]int{
|
spec := map[string]int{
|
||||||
user: 3,
|
user: 3,
|
||||||
|
@@ -51,11 +51,8 @@ var (
|
|||||||
tailscaleVersions2021 = map[string]bool{
|
tailscaleVersions2021 = map[string]bool{
|
||||||
"head": true,
|
"head": true,
|
||||||
"unstable": true,
|
"unstable": true,
|
||||||
"1.66": true, // CapVer: not checked
|
"1.60": true, // CapVer: 82
|
||||||
"1.64": true, // CapVer: not checked
|
"1.58": true, // CapVer: 82
|
||||||
"1.62": true, // CapVer: not checked
|
|
||||||
"1.60": true, // CapVer: not checked
|
|
||||||
"1.58": true, // CapVer: not checked
|
|
||||||
"1.56": true, // CapVer: 82
|
"1.56": true, // CapVer: 82
|
||||||
"1.54": true, // CapVer: 79
|
"1.54": true, // CapVer: 79
|
||||||
"1.52": true, // CapVer: 79
|
"1.52": true, // CapVer: 79
|
||||||
@@ -426,10 +423,8 @@ func (s *Scenario) WaitForTailscaleSync() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
for _, user := range s.users {
|
for _, user := range s.users {
|
||||||
for _, client := range user.Clients {
|
for _, client := range user.Clients {
|
||||||
peers, allOnline, _ := client.FailingPeersAsString()
|
peers, _ := client.PrettyPeers()
|
||||||
if !allOnline {
|
log.Println(peers)
|
||||||
log.Println(peers)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -455,7 +450,7 @@ func (s *Scenario) WaitForTailscaleSyncWithPeerCount(peerCount int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateHeadscaleEnv is a convenient method returning a complete Headcale
|
// CreateHeadscaleEnv is a conventient method returning a complete Headcale
|
||||||
// test environment with nodes of all versions, joined to the server with X
|
// test environment with nodes of all versions, joined to the server with X
|
||||||
// users.
|
// users.
|
||||||
func (s *Scenario) CreateHeadscaleEnv(
|
func (s *Scenario) CreateHeadscaleEnv(
|
||||||
|
@@ -36,8 +36,5 @@ type TailscaleClient interface {
|
|||||||
Ping(hostnameOrIP string, opts ...tsic.PingOption) error
|
Ping(hostnameOrIP string, opts ...tsic.PingOption) error
|
||||||
Curl(url string, opts ...tsic.CurlOption) (string, error)
|
Curl(url string, opts ...tsic.CurlOption) (string, error)
|
||||||
ID() string
|
ID() string
|
||||||
|
PrettyPeers() (string, error)
|
||||||
// FailingPeersAsString returns a formatted-ish multi-line-string of peers in the client
|
|
||||||
// and a bool indicating if the clients online count and peer count is equal.
|
|
||||||
FailingPeersAsString() (string, bool, error)
|
|
||||||
}
|
}
|
||||||
|
@@ -691,18 +691,15 @@ func (t *TailscaleInContainer) FQDN() (string, error) {
|
|||||||
return status.Self.DNSName, nil
|
return status.Self.DNSName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FailingPeersAsString returns a formatted-ish multi-line-string of peers in the client
|
// PrettyPeers returns a formatted-ish table of peers in the client.
|
||||||
// and a bool indicating if the clients online count and peer count is equal.
|
func (t *TailscaleInContainer) PrettyPeers() (string, error) {
|
||||||
func (t *TailscaleInContainer) FailingPeersAsString() (string, bool, error) {
|
|
||||||
status, err := t.Status()
|
status, err := t.Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, fmt.Errorf("failed to get FQDN: %w", err)
|
return "", fmt.Errorf("failed to get FQDN: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var b strings.Builder
|
str := fmt.Sprintf("Peers of %s\n", t.hostname)
|
||||||
|
str += "Hostname\tOnline\tLastSeen\n"
|
||||||
fmt.Fprintf(&b, "Peers of %s\n", t.hostname)
|
|
||||||
fmt.Fprint(&b, "Hostname\tOnline\tLastSeen\n")
|
|
||||||
|
|
||||||
peerCount := len(status.Peers())
|
peerCount := len(status.Peers())
|
||||||
onlineCount := 0
|
onlineCount := 0
|
||||||
@@ -714,12 +711,12 @@ func (t *TailscaleInContainer) FailingPeersAsString() (string, bool, error) {
|
|||||||
onlineCount++
|
onlineCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(&b, "%s\t%t\t%s\n", peer.HostName, peer.Online, peer.LastSeen)
|
str += fmt.Sprintf("%s\t%t\t%s\n", peer.HostName, peer.Online, peer.LastSeen)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(&b, "Peer Count: %d, Online Count: %d\n\n", peerCount, onlineCount)
|
str += fmt.Sprintf("Peer Count: %d, Online Count: %d\n\n", peerCount, onlineCount)
|
||||||
|
|
||||||
return b.String(), peerCount == onlineCount, nil
|
return str, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitForNeedsLogin blocks until the Tailscale (tailscaled) instance has
|
// WaitForNeedsLogin blocks until the Tailscale (tailscaled) instance has
|
||||||
|
@@ -331,7 +331,7 @@ func dockertestMaxWait() time.Duration {
|
|||||||
// return timeout
|
// return timeout
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// pingAllNegativeHelper is intended to have 1 or more nodes timing out from the ping,
|
// pingAllNegativeHelper is intended to have 1 or more nodes timeing out from the ping,
|
||||||
// it counts failures instead of successes.
|
// it counts failures instead of successes.
|
||||||
// func pingAllNegativeHelper(t *testing.T, clients []TailscaleClient, addrs []string) int {
|
// func pingAllNegativeHelper(t *testing.T, clients []TailscaleClient, addrs []string) int {
|
||||||
// t.Helper()
|
// t.Helper()
|
||||||
|
Reference in New Issue
Block a user