Compare commits
6 Commits
will/conta
...
kube_exp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23d8ccce34 | ||
|
|
b0d87c9c80 | ||
|
|
4b43419923 | ||
|
|
713ed80a09 | ||
|
|
a9f37b852d | ||
|
|
5ad0dce5c0 |
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -47,12 +47,6 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Install a more recent Go that understands modern go.mod content.
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
|
||||
35
.github/workflows/govulncheck.yml
vendored
35
.github/workflows/govulncheck.yml
vendored
@@ -22,30 +22,17 @@ jobs:
|
||||
- name: Scan source code for known vulnerabilities
|
||||
run: PATH=$PWD/tool/:$PATH "$(./tool/go env GOPATH)/bin/govulncheck" -test ./...
|
||||
|
||||
- name: Post to slack
|
||||
if: failure() && github.event_name == 'schedule'
|
||||
uses: slackapi/slack-github-action@v1.24.0
|
||||
env:
|
||||
SLACK_BOT_TOKEN: ${{ secrets.GOVULNCHECK_BOT_TOKEN }}
|
||||
- uses: ruby/action-slack@v3.2.1
|
||||
with:
|
||||
channel-id: 'C05PXRM304B'
|
||||
payload: |
|
||||
payload: >
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "Govulncheck failed in ${{ github.repository }}"
|
||||
},
|
||||
"accessory": {
|
||||
"type": "button",
|
||||
"text": {
|
||||
"type": "plain_text",
|
||||
"text": "View results"
|
||||
},
|
||||
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
}
|
||||
}
|
||||
]
|
||||
"attachments": [{
|
||||
"title": "${{ job.status }}: ${{ github.workflow }}",
|
||||
"title_link": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks",
|
||||
"text": "${{ github.repository }}@${{ github.sha }}",
|
||||
"color": "danger"
|
||||
}]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
if: failure() && github.event_name == 'schedule'
|
||||
|
||||
4
.github/workflows/kubemanifests.yaml
vendored
4
.github/workflows/kubemanifests.yaml
vendored
@@ -2,8 +2,8 @@ name: "Kubernetes manifests"
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- './cmd/k8s-operator/**'
|
||||
- './k8s-operator/**'
|
||||
- './cmd/k8s-operator/'
|
||||
- './k8s-operator/'
|
||||
- '.github/workflows/kubemanifests.yaml'
|
||||
|
||||
# Cancel workflow run if there is a newer push to the same PR for which it is
|
||||
|
||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -241,9 +241,9 @@ jobs:
|
||||
goarch: amd64
|
||||
- goos: openbsd
|
||||
goarch: amd64
|
||||
# Plan9 (disabled until 3p dependencies are fixed)
|
||||
# - goos: plan9
|
||||
# goarch: amd64
|
||||
# Plan9
|
||||
- goos: plan9
|
||||
goarch: amd64
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
|
||||
12
Makefile
12
Makefile
@@ -3,8 +3,6 @@ SYNO_ARCH ?= "amd64"
|
||||
SYNO_DSM ?= "7"
|
||||
TAGS ?= "latest"
|
||||
|
||||
PLATFORM ?= "flyio" ## flyio==linux/amd64. Set to "" to build all platforms.
|
||||
|
||||
vet: ## Run go vet
|
||||
./tool/go vet ./...
|
||||
|
||||
@@ -20,8 +18,7 @@ updatedeps: ## Update depaware deps
|
||||
PATH="$$(./tool/go env GOROOT)/bin:$$PATH" ./tool/go run github.com/tailscale/depaware --update \
|
||||
tailscale.com/cmd/tailscaled \
|
||||
tailscale.com/cmd/tailscale \
|
||||
tailscale.com/cmd/derper \
|
||||
tailscale.com/cmd/stund
|
||||
tailscale.com/cmd/derper
|
||||
|
||||
depaware: ## Run depaware checks
|
||||
# depaware (via x/tools/go/packages) shells back to "go", so make sure the "go"
|
||||
@@ -29,8 +26,7 @@ depaware: ## Run depaware checks
|
||||
PATH="$$(./tool/go env GOROOT)/bin:$$PATH" ./tool/go run github.com/tailscale/depaware --check \
|
||||
tailscale.com/cmd/tailscaled \
|
||||
tailscale.com/cmd/tailscale \
|
||||
tailscale.com/cmd/derper \
|
||||
tailscale.com/cmd/stund
|
||||
tailscale.com/cmd/derper
|
||||
|
||||
buildwindows: ## Build tailscale CLI for windows/amd64
|
||||
GOOS=windows GOARCH=amd64 ./tool/go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled
|
||||
@@ -90,7 +86,7 @@ publishdevimage: ## Build and publish tailscale image to location specified by $
|
||||
@test "${REPO}" != "ghcr.io/tailscale/tailscale" || (echo "REPO=... must not be ghcr.io/tailscale/tailscale" && exit 1)
|
||||
@test "${REPO}" != "tailscale/k8s-operator" || (echo "REPO=... must not be tailscale/k8s-operator" && exit 1)
|
||||
@test "${REPO}" != "ghcr.io/tailscale/k8s-operator" || (echo "REPO=... must not be ghcr.io/tailscale/k8s-operator" && exit 1)
|
||||
TAGS="${TAGS}" REPOS=${REPO} PLATFORM=${PLATFORM} PUSH=true TARGET=client ./build_docker.sh
|
||||
TAGS="${TAGS}" REPOS=${REPO} PUSH=true TARGET=client ./build_docker.sh
|
||||
|
||||
publishdevoperator: ## Build and publish k8s-operator image to location specified by ${REPO}
|
||||
@test -n "${REPO}" || (echo "REPO=... required; e.g. REPO=ghcr.io/${USER}/tailscale" && exit 1)
|
||||
@@ -98,7 +94,7 @@ publishdevoperator: ## Build and publish k8s-operator image to location specifie
|
||||
@test "${REPO}" != "ghcr.io/tailscale/tailscale" || (echo "REPO=... must not be ghcr.io/tailscale/tailscale" && exit 1)
|
||||
@test "${REPO}" != "tailscale/k8s-operator" || (echo "REPO=... must not be tailscale/k8s-operator" && exit 1)
|
||||
@test "${REPO}" != "ghcr.io/tailscale/k8s-operator" || (echo "REPO=... must not be ghcr.io/tailscale/k8s-operator" && exit 1)
|
||||
TAGS="${TAGS}" REPOS=${REPO} PLATFORM=${PLATFORM} PUSH=true TARGET=operator ./build_docker.sh
|
||||
TAGS="${TAGS}" REPOS=${REPO} PUSH=true TARGET=operator ./build_docker.sh
|
||||
|
||||
help: ## Show this help
|
||||
@echo "\nSpecify a command. The choices are:\n"
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.59.0
|
||||
1.57.0
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
package appc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -21,18 +20,14 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/util/execqueue"
|
||||
)
|
||||
|
||||
// RouteAdvertiser is an interface that allows the AppConnector to advertise
|
||||
// newly discovered routes that need to be served through the AppConnector.
|
||||
type RouteAdvertiser interface {
|
||||
// AdvertiseRoute adds one or more route advertisements skipping any that
|
||||
// are already advertised.
|
||||
AdvertiseRoute(...netip.Prefix) error
|
||||
|
||||
// UnadvertiseRoute removes any matching route advertisements.
|
||||
UnadvertiseRoute(...netip.Prefix) error
|
||||
// AdvertiseRoute adds a new route advertisement if the route is not already
|
||||
// being advertised.
|
||||
AdvertiseRoute(netip.Prefix) error
|
||||
}
|
||||
|
||||
// AppConnector is an implementation of an AppConnector that performs
|
||||
@@ -50,19 +45,12 @@ type AppConnector struct {
|
||||
|
||||
// mu guards the fields that follow
|
||||
mu sync.Mutex
|
||||
|
||||
// domains is a map of lower case domain names with no trailing dot, to a
|
||||
// list of resolved IP addresses.
|
||||
domains map[string][]netip.Addr
|
||||
|
||||
// controlRoutes is the list of routes that were last supplied by control.
|
||||
controlRoutes []netip.Prefix
|
||||
|
||||
// wildcards is the list of domain strings that match subdomains.
|
||||
wildcards []string
|
||||
|
||||
// queue provides ordering for update operations
|
||||
queue execqueue.ExecQueue
|
||||
}
|
||||
|
||||
// NewAppConnector creates a new AppConnector.
|
||||
@@ -73,33 +61,11 @@ func NewAppConnector(logf logger.Logf, routeAdvertiser RouteAdvertiser) *AppConn
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateDomainsAndRoutes starts an asynchronous update of the configuration
|
||||
// given the new domains and routes.
|
||||
func (e *AppConnector) UpdateDomainsAndRoutes(domains []string, routes []netip.Prefix) {
|
||||
e.queue.Add(func() {
|
||||
// Add the new routes first.
|
||||
e.updateRoutes(routes)
|
||||
e.updateDomains(domains)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateDomains asynchronously replaces the current set of configured domains
|
||||
// with the supplied set of domains. Domains must not contain a trailing dot,
|
||||
// and should be lower case. If the domain contains a leading '*' label it
|
||||
// matches all subdomains of a domain.
|
||||
// UpdateDomains replaces the current set of configured domains with the
|
||||
// supplied set of domains. Domains must not contain a trailing dot, and should
|
||||
// be lower case. If the domain contains a leading '*' label it matches all
|
||||
// subdomains of a domain.
|
||||
func (e *AppConnector) UpdateDomains(domains []string) {
|
||||
e.queue.Add(func() {
|
||||
e.updateDomains(domains)
|
||||
})
|
||||
}
|
||||
|
||||
// Wait waits for the currently scheduled asynchronous configuration changes to
|
||||
// complete.
|
||||
func (e *AppConnector) Wait(ctx context.Context) {
|
||||
e.queue.Wait(ctx)
|
||||
}
|
||||
|
||||
func (e *AppConnector) updateDomains(domains []string) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
@@ -131,46 +97,6 @@ func (e *AppConnector) updateDomains(domains []string) {
|
||||
e.logf("handling domains: %v and wildcards: %v", xmaps.Keys(e.domains), e.wildcards)
|
||||
}
|
||||
|
||||
// updateRoutes merges the supplied routes into the currently configured routes. The routes supplied
|
||||
// by control for UpdateRoutes are supplemental to the routes discovered by DNS resolution, but are
|
||||
// also more often whole ranges. UpdateRoutes will remove any single address routes that are now
|
||||
// covered by new ranges.
|
||||
func (e *AppConnector) updateRoutes(routes []netip.Prefix) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
// If there was no change since the last update, no work to do.
|
||||
if slices.Equal(e.controlRoutes, routes) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := e.routeAdvertiser.AdvertiseRoute(routes...); err != nil {
|
||||
e.logf("failed to advertise routes: %v: %v", routes, err)
|
||||
return
|
||||
}
|
||||
|
||||
var toRemove []netip.Prefix
|
||||
|
||||
nextRoute:
|
||||
for _, r := range routes {
|
||||
for _, addr := range e.domains {
|
||||
for _, a := range addr {
|
||||
if r.Contains(a) && netip.PrefixFrom(a, a.BitLen()) != r {
|
||||
pfx := netip.PrefixFrom(a, a.BitLen())
|
||||
toRemove = append(toRemove, pfx)
|
||||
continue nextRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.routeAdvertiser.UnadvertiseRoute(toRemove...); err != nil {
|
||||
e.logf("failed to unadvertise routes: %v: %v", toRemove, err)
|
||||
}
|
||||
|
||||
e.controlRoutes = routes
|
||||
}
|
||||
|
||||
// Domains returns the currently configured domain list.
|
||||
func (e *AppConnector) Domains() views.Slice[string] {
|
||||
e.mu.Lock()
|
||||
@@ -206,7 +132,6 @@ func (e *AppConnector) ObserveDNSResponse(res []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
nextAnswer:
|
||||
for {
|
||||
h, err := p.AnswerHeader()
|
||||
if err == dnsmessage.ErrSectionDone {
|
||||
@@ -281,16 +206,6 @@ nextAnswer:
|
||||
if slices.Contains(addrs, addr) {
|
||||
continue
|
||||
}
|
||||
for _, route := range e.controlRoutes {
|
||||
if route.Contains(addr) {
|
||||
// record the new address associated with the domain for faster matching in subsequent
|
||||
// requests and for diagnostic records.
|
||||
e.mu.Lock()
|
||||
e.domains[domain] = append(addrs, addr)
|
||||
e.mu.Unlock()
|
||||
continue nextAnswer
|
||||
}
|
||||
}
|
||||
if err := e.routeAdvertiser.AdvertiseRoute(netip.PrefixFrom(addr, addr.BitLen())); err != nil {
|
||||
e.logf("failed to advertise route for %s: %v: %v", domain, addr, err)
|
||||
continue
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package appc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"slices"
|
||||
@@ -12,17 +11,12 @@ import (
|
||||
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"tailscale.com/appc/appctest"
|
||||
"tailscale.com/util/mak"
|
||||
"tailscale.com/util/must"
|
||||
)
|
||||
|
||||
func TestUpdateDomains(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
a := NewAppConnector(t.Logf, nil)
|
||||
a.UpdateDomains([]string{"example.com"})
|
||||
|
||||
a.Wait(ctx)
|
||||
if got, want := a.Domains().AsSlice(), []string{"example.com"}; !slices.Equal(got, want) {
|
||||
t.Errorf("got %v; want %v", got, want)
|
||||
}
|
||||
@@ -30,7 +24,6 @@ func TestUpdateDomains(t *testing.T) {
|
||||
addr := netip.MustParseAddr("192.0.0.8")
|
||||
a.domains["example.com"] = append(a.domains["example.com"], addr)
|
||||
a.UpdateDomains([]string{"example.com"})
|
||||
a.Wait(ctx)
|
||||
|
||||
if got, want := a.domains["example.com"], []netip.Addr{addr}; !slices.Equal(got, want) {
|
||||
t.Errorf("got %v; want %v", got, want)
|
||||
@@ -38,66 +31,15 @@ func TestUpdateDomains(t *testing.T) {
|
||||
|
||||
// domains are explicitly downcased on set.
|
||||
a.UpdateDomains([]string{"UP.EXAMPLE.COM"})
|
||||
a.Wait(ctx)
|
||||
if got, want := xmaps.Keys(a.domains), []string{"up.example.com"}; !slices.Equal(got, want) {
|
||||
t.Errorf("got %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRoutes(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
rc := &appctest.RouteCollector{}
|
||||
a := NewAppConnector(t.Logf, rc)
|
||||
a.updateDomains([]string{"*.example.com"})
|
||||
|
||||
// This route should be collapsed into the range
|
||||
a.ObserveDNSResponse(dnsResponse("a.example.com.", "192.0.2.1"))
|
||||
a.Wait(ctx)
|
||||
|
||||
if !slices.Equal(rc.Routes(), []netip.Prefix{netip.MustParsePrefix("192.0.2.1/32")}) {
|
||||
t.Fatalf("got %v, want %v", rc.Routes(), []netip.Prefix{netip.MustParsePrefix("192.0.2.1/32")})
|
||||
}
|
||||
|
||||
// This route should not be collapsed or removed
|
||||
a.ObserveDNSResponse(dnsResponse("b.example.com.", "192.0.0.1"))
|
||||
a.Wait(ctx)
|
||||
|
||||
routes := []netip.Prefix{netip.MustParsePrefix("192.0.2.0/24"), netip.MustParsePrefix("192.0.0.1/32")}
|
||||
a.updateRoutes(routes)
|
||||
|
||||
slices.SortFunc(rc.Routes(), prefixCompare)
|
||||
rc.SetRoutes(slices.Compact(rc.Routes()))
|
||||
slices.SortFunc(routes, prefixCompare)
|
||||
|
||||
// Ensure that the non-matching /32 is preserved, even though it's in the domains table.
|
||||
if !slices.EqualFunc(routes, rc.Routes(), prefixEqual) {
|
||||
t.Errorf("added routes: got %v, want %v", rc.Routes(), routes)
|
||||
}
|
||||
|
||||
// Ensure that the contained /32 is removed, replaced by the /24.
|
||||
wantRemoved := []netip.Prefix{netip.MustParsePrefix("192.0.2.1/32")}
|
||||
if !slices.EqualFunc(rc.RemovedRoutes(), wantRemoved, prefixEqual) {
|
||||
t.Fatalf("unexpected removed routes: %v", rc.RemovedRoutes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRoutesUnadvertisesContainedRoutes(t *testing.T) {
|
||||
rc := &appctest.RouteCollector{}
|
||||
a := NewAppConnector(t.Logf, rc)
|
||||
mak.Set(&a.domains, "example.com", []netip.Addr{netip.MustParseAddr("192.0.2.1")})
|
||||
rc.SetRoutes([]netip.Prefix{netip.MustParsePrefix("192.0.2.1/32")})
|
||||
routes := []netip.Prefix{netip.MustParsePrefix("192.0.2.0/24")}
|
||||
a.updateRoutes(routes)
|
||||
|
||||
if !slices.EqualFunc(routes, rc.Routes(), prefixEqual) {
|
||||
t.Fatalf("got %v, want %v", rc.Routes(), routes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainRoutes(t *testing.T) {
|
||||
rc := &appctest.RouteCollector{}
|
||||
rc := &routeCollector{}
|
||||
a := NewAppConnector(t.Logf, rc)
|
||||
a.updateDomains([]string{"example.com"})
|
||||
a.UpdateDomains([]string{"example.com"})
|
||||
a.ObserveDNSResponse(dnsResponse("example.com.", "192.0.0.8"))
|
||||
|
||||
want := map[string][]netip.Addr{
|
||||
@@ -110,63 +52,51 @@ func TestDomainRoutes(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestObserveDNSResponse(t *testing.T) {
|
||||
rc := &appctest.RouteCollector{}
|
||||
rc := &routeCollector{}
|
||||
a := NewAppConnector(t.Logf, rc)
|
||||
|
||||
// a has no domains configured, so it should not advertise any routes
|
||||
a.ObserveDNSResponse(dnsResponse("example.com.", "192.0.0.8"))
|
||||
if got, want := rc.Routes(), ([]netip.Prefix)(nil); !slices.Equal(got, want) {
|
||||
if got, want := rc.routes, ([]netip.Prefix)(nil); !slices.Equal(got, want) {
|
||||
t.Errorf("got %v; want %v", got, want)
|
||||
}
|
||||
|
||||
wantRoutes := []netip.Prefix{netip.MustParsePrefix("192.0.0.8/32")}
|
||||
|
||||
a.updateDomains([]string{"example.com"})
|
||||
a.UpdateDomains([]string{"example.com"})
|
||||
a.ObserveDNSResponse(dnsResponse("example.com.", "192.0.0.8"))
|
||||
if got, want := rc.Routes(), wantRoutes; !slices.Equal(got, want) {
|
||||
if got, want := rc.routes, wantRoutes; !slices.Equal(got, want) {
|
||||
t.Errorf("got %v; want %v", got, want)
|
||||
}
|
||||
|
||||
wantRoutes = append(wantRoutes, netip.MustParsePrefix("2001:db8::1/128"))
|
||||
|
||||
a.ObserveDNSResponse(dnsResponse("example.com.", "2001:db8::1"))
|
||||
if got, want := rc.Routes(), wantRoutes; !slices.Equal(got, want) {
|
||||
if got, want := rc.routes, wantRoutes; !slices.Equal(got, want) {
|
||||
t.Errorf("got %v; want %v", got, want)
|
||||
}
|
||||
|
||||
// don't re-advertise routes that have already been advertised
|
||||
a.ObserveDNSResponse(dnsResponse("example.com.", "2001:db8::1"))
|
||||
if !slices.Equal(rc.Routes(), wantRoutes) {
|
||||
t.Errorf("rc.Routes(): got %v; want %v", rc.Routes(), wantRoutes)
|
||||
}
|
||||
|
||||
// don't advertise addresses that are already in a control provided route
|
||||
pfx := netip.MustParsePrefix("192.0.2.0/24")
|
||||
a.updateRoutes([]netip.Prefix{pfx})
|
||||
wantRoutes = append(wantRoutes, pfx)
|
||||
a.ObserveDNSResponse(dnsResponse("example.com.", "192.0.2.1"))
|
||||
if !slices.Equal(rc.Routes(), wantRoutes) {
|
||||
t.Errorf("rc.Routes(): got %v; want %v", rc.Routes(), wantRoutes)
|
||||
}
|
||||
if !slices.Contains(a.domains["example.com"], netip.MustParseAddr("192.0.2.1")) {
|
||||
t.Errorf("missing %v from %v", "192.0.2.1", a.domains["exmaple.com"])
|
||||
if !slices.Equal(rc.routes, wantRoutes) {
|
||||
t.Errorf("got %v; want %v", rc.routes, wantRoutes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWildcardDomains(t *testing.T) {
|
||||
rc := &appctest.RouteCollector{}
|
||||
rc := &routeCollector{}
|
||||
a := NewAppConnector(t.Logf, rc)
|
||||
|
||||
a.updateDomains([]string{"*.example.com"})
|
||||
a.UpdateDomains([]string{"*.example.com"})
|
||||
a.ObserveDNSResponse(dnsResponse("foo.example.com.", "192.0.0.8"))
|
||||
if got, want := rc.Routes(), []netip.Prefix{netip.MustParsePrefix("192.0.0.8/32")}; !slices.Equal(got, want) {
|
||||
if got, want := rc.routes, []netip.Prefix{netip.MustParsePrefix("192.0.0.8/32")}; !slices.Equal(got, want) {
|
||||
t.Errorf("routes: got %v; want %v", got, want)
|
||||
}
|
||||
if got, want := a.wildcards, []string{"example.com"}; !slices.Equal(got, want) {
|
||||
t.Errorf("wildcards: got %v; want %v", got, want)
|
||||
}
|
||||
|
||||
a.updateDomains([]string{"*.example.com", "example.com"})
|
||||
a.UpdateDomains([]string{"*.example.com", "example.com"})
|
||||
if _, ok := a.domains["foo.example.com"]; !ok {
|
||||
t.Errorf("expected foo.example.com to be preserved in domains due to wildcard")
|
||||
}
|
||||
@@ -175,7 +105,7 @@ func TestWildcardDomains(t *testing.T) {
|
||||
}
|
||||
|
||||
// There was an early regression where the wildcard domain was added repeatedly, this guards against that.
|
||||
a.updateDomains([]string{"*.example.com", "example.com"})
|
||||
a.UpdateDomains([]string{"*.example.com", "example.com"})
|
||||
if len(a.wildcards) != 1 {
|
||||
t.Errorf("expected only one wildcard domain, got %v", a.wildcards)
|
||||
}
|
||||
@@ -218,13 +148,15 @@ func dnsResponse(domain, address string) []byte {
|
||||
return must.Get(b.Finish())
|
||||
}
|
||||
|
||||
func prefixEqual(a, b netip.Prefix) bool {
|
||||
return a == b
|
||||
// routeCollector is a test helper that collects the list of routes advertised
|
||||
type routeCollector struct {
|
||||
routes []netip.Prefix
|
||||
}
|
||||
|
||||
func prefixCompare(a, b netip.Prefix) int {
|
||||
if a.Addr().Compare(b.Addr()) == 0 {
|
||||
return a.Bits() - b.Bits()
|
||||
}
|
||||
return a.Addr().Compare(b.Addr())
|
||||
// routeCollector implements RouteAdvertiser
|
||||
var _ RouteAdvertiser = (*routeCollector)(nil)
|
||||
|
||||
func (rc *routeCollector) AdvertiseRoute(pfx netip.Prefix) error {
|
||||
rc.routes = append(rc.routes, pfx)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package appctest
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// RouteCollector is a test helper that collects the list of routes advertised
|
||||
type RouteCollector struct {
|
||||
routes []netip.Prefix
|
||||
removedRoutes []netip.Prefix
|
||||
}
|
||||
|
||||
func (rc *RouteCollector) AdvertiseRoute(pfx ...netip.Prefix) error {
|
||||
rc.routes = append(rc.routes, pfx...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *RouteCollector) UnadvertiseRoute(toRemove ...netip.Prefix) error {
|
||||
routes := rc.routes
|
||||
rc.routes = rc.routes[:0]
|
||||
for _, r := range routes {
|
||||
if !slices.Contains(toRemove, r) {
|
||||
rc.routes = append(rc.routes, r)
|
||||
} else {
|
||||
rc.removedRoutes = append(rc.removedRoutes, r)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovedRoutes returns the list of routes that were removed.
|
||||
func (rc *RouteCollector) RemovedRoutes() []netip.Prefix {
|
||||
return rc.removedRoutes
|
||||
}
|
||||
|
||||
// Routes returns the ordered list of routes that were added, including
|
||||
// possible duplicates.
|
||||
func (rc *RouteCollector) Routes() []netip.Prefix {
|
||||
return rc.routes
|
||||
}
|
||||
|
||||
func (rc *RouteCollector) SetRoutes(routes []netip.Prefix) error {
|
||||
rc.routes = routes
|
||||
return nil
|
||||
}
|
||||
@@ -32,7 +32,6 @@ PUSH="${PUSH:-false}"
|
||||
TARGET="${TARGET:-${DEFAULT_TARGET}}"
|
||||
TAGS="${TAGS:-${DEFAULT_TAGS}}"
|
||||
BASE="${BASE:-${DEFAULT_BASE}}"
|
||||
PLATFORM="${PLATFORM:-}" # default to all platforms
|
||||
|
||||
case "$TARGET" in
|
||||
client)
|
||||
@@ -51,7 +50,6 @@ case "$TARGET" in
|
||||
--tags="${TAGS}" \
|
||||
--repos="${REPOS}" \
|
||||
--push="${PUSH}" \
|
||||
--target="${PLATFORM}" \
|
||||
/usr/local/bin/containerboot
|
||||
;;
|
||||
operator)
|
||||
@@ -67,7 +65,6 @@ case "$TARGET" in
|
||||
--tags="${TAGS}" \
|
||||
--repos="${REPOS}" \
|
||||
--push="${PUSH}" \
|
||||
--target="${PLATFORM}" \
|
||||
/usr/local/bin/operator
|
||||
;;
|
||||
*)
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -233,55 +232,3 @@ func (s *Server) newSessionID() (string, error) {
|
||||
}
|
||||
return "", errors.New("too many collisions generating new session; please refresh page")
|
||||
}
|
||||
|
||||
type peerCapabilities map[capFeature]bool // value is true if the peer can edit the given feature
|
||||
|
||||
// canEdit is true if the peerCapabilities grant edit access
|
||||
// to the given feature.
|
||||
func (p peerCapabilities) canEdit(feature capFeature) bool {
|
||||
if p == nil {
|
||||
return false
|
||||
}
|
||||
if p[capFeatureAll] {
|
||||
return true
|
||||
}
|
||||
return p[feature]
|
||||
}
|
||||
|
||||
type capFeature string
|
||||
|
||||
const (
|
||||
// The following values should not be edited.
|
||||
// New caps can be added, but existing ones should not be changed,
|
||||
// as these exact values are used by users in tailnet policy files.
|
||||
|
||||
capFeatureAll capFeature = "*" // grants peer management of all features
|
||||
capFeatureFunnel capFeature = "funnel" // grants peer serve/funnel management
|
||||
capFeatureSSH capFeature = "ssh" // grants peer SSH server management
|
||||
capFeatureSubnet capFeature = "subnet" // grants peer subnet routes management
|
||||
capFeatureExitNode capFeature = "exitnode" // grants peer ability to advertise-as and use exit nodes
|
||||
capFeatureAccount capFeature = "account" // grants peer ability to turn on auto updates and log out of node
|
||||
)
|
||||
|
||||
type capRule struct {
|
||||
CanEdit []string `json:"canEdit,omitempty"` // list of features peer is allowed to edit
|
||||
}
|
||||
|
||||
// toPeerCapabilities parses out the web ui capabilities from the
|
||||
// given whois response.
|
||||
func toPeerCapabilities(whois *apitype.WhoIsResponse) (peerCapabilities, error) {
|
||||
caps := peerCapabilities{}
|
||||
if whois == nil {
|
||||
return caps, nil
|
||||
}
|
||||
rules, err := tailcfg.UnmarshalCapJSON[capRule](whois.CapMap, tailcfg.PeerCapabilityWebUI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal capability: %v", err)
|
||||
}
|
||||
for _, c := range rules {
|
||||
for _, f := range c.CanEdit {
|
||||
caps[capFeature(strings.ToLower(f))] = true
|
||||
}
|
||||
}
|
||||
return caps, nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import React from "react"
|
||||
import { ReactComponent as TailscaleIcon } from "src/assets/icons/tailscale-icon.svg"
|
||||
import LoginToggle from "src/components/login-toggle"
|
||||
import DeviceDetailsView from "src/components/views/device-details-view"
|
||||
import DisconnectedView from "src/components/views/disconnected-view"
|
||||
import HomeView from "src/components/views/home-view"
|
||||
import LoginView from "src/components/views/login-view"
|
||||
import SSHView from "src/components/views/ssh-view"
|
||||
@@ -75,7 +74,9 @@ function WebClient({
|
||||
/>
|
||||
</FeatureRoute>
|
||||
<Route path="/disconnected">
|
||||
<DisconnectedView />
|
||||
<Card className="mt-8">
|
||||
<EmptyState description="You have been disconnected" />
|
||||
</Card>
|
||||
</Route>
|
||||
<Route>
|
||||
<Card className="mt-8">
|
||||
|
||||
@@ -95,16 +95,9 @@ function LoginPopoverContent({
|
||||
const [canConnectOverTS, setCanConnectOverTS] = useState<boolean>(false)
|
||||
const [isRunningCheck, setIsRunningCheck] = useState<boolean>(false)
|
||||
|
||||
// Whether the current page is loaded over HTTPS.
|
||||
// If it is, then the connectivity check to the management client
|
||||
// will fail with a mixed-content error.
|
||||
const isHTTPS = window.location.protocol === "https:"
|
||||
|
||||
const checkTSConnection = useCallback(() => {
|
||||
if (auth.viewerIdentity || isHTTPS) {
|
||||
// Skip the connectivity check if we either already know we're connected over Tailscale,
|
||||
// or know the connectivity check will fail because the current page is loaded over HTTPS.
|
||||
setCanConnectOverTS(true)
|
||||
if (auth.viewerIdentity) {
|
||||
setCanConnectOverTS(true) // already connected over ts
|
||||
return
|
||||
}
|
||||
// Otherwise, test connection to the ts IP.
|
||||
@@ -118,7 +111,7 @@ function LoginPopoverContent({
|
||||
setIsRunningCheck(false)
|
||||
})
|
||||
.catch(() => setIsRunningCheck(false))
|
||||
}, [auth.viewerIdentity, isRunningCheck, node.IPv4, isHTTPS])
|
||||
}, [auth.viewerIdentity, isRunningCheck, node.IPv4])
|
||||
|
||||
/**
|
||||
* Checking connection for first time on page load.
|
||||
@@ -200,14 +193,6 @@ function LoginPopoverContent({
|
||||
You can see most of this device's details. To make changes,
|
||||
you need to sign in.
|
||||
</p>
|
||||
{isHTTPS && (
|
||||
// we don't know if the user can connect over TS, so
|
||||
// provide extra tips in case they have trouble.
|
||||
<p className="text-gray-500 text-xs font-semibold pt-2">
|
||||
Make sure you are connected to your tailnet, and that your
|
||||
policy file allows access.
|
||||
</p>
|
||||
)}
|
||||
<SignInButton auth={auth} onClick={handleSignInClick} />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -226,22 +226,24 @@ function DisconnectDialog() {
|
||||
return (
|
||||
<Dialog
|
||||
className="max-w-md"
|
||||
title="Log out"
|
||||
trigger={<Button sizeVariant="small">Log out…</Button>}
|
||||
title="Disconnect"
|
||||
trigger={<Button sizeVariant="small">Disconnect…</Button>}
|
||||
>
|
||||
<Dialog.Form
|
||||
cancelButton
|
||||
submitButton="Log out"
|
||||
submitButton="Disconnect"
|
||||
destructive
|
||||
onSubmit={() => {
|
||||
api({ action: "logout" })
|
||||
setLocation("/disconnected")
|
||||
}}
|
||||
>
|
||||
Logging out of this device will disconnect it from your tailnet and
|
||||
expire its node key. You won’t be able to use this web interface until
|
||||
you re-authenticate the device from either the Tailscale app or the
|
||||
Tailscale command line interface.
|
||||
You are about to disconnect this device from your tailnet. To reconnect,
|
||||
you will be required to re-authenticate this device.
|
||||
<p className="mt-4 text-sm text-text-muted">
|
||||
Your connection to this web interface will end as soon as you click
|
||||
disconnect.
|
||||
</p>
|
||||
</Dialog.Form>
|
||||
</Dialog>
|
||||
)
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import React from "react"
|
||||
import { ReactComponent as TailscaleIcon } from "src/assets/icons/tailscale-icon.svg"
|
||||
|
||||
/**
|
||||
* DisconnectedView is rendered after node logout.
|
||||
*/
|
||||
export default function DisconnectedView() {
|
||||
return (
|
||||
<>
|
||||
<TailscaleIcon className="mx-auto" />
|
||||
<p className="mt-12 text-center text-text-muted">
|
||||
You logged out of this device. To reconnect it you will have to
|
||||
re-authenticate the device from either the Tailscale app or the
|
||||
Tailscale command line interface.
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -176,12 +176,11 @@ func NewServer(opts ServerOpts) (s *Server, err error) {
|
||||
waitAuthURL: opts.WaitAuthURL,
|
||||
}
|
||||
if opts.PathPrefix != "" {
|
||||
// Enforce that path prefix always has a single leading '/'
|
||||
// so that it is treated as a relative URL path.
|
||||
// We strip multiple leading '/' to prevent schema-less offsite URLs like "//example.com".
|
||||
//
|
||||
// See https://github.com/tailscale/corp/issues/16268.
|
||||
s.pathPrefix = "/" + strings.TrimLeft(path.Clean(opts.PathPrefix), "/\\")
|
||||
// In enforcePrefix, we add the necessary leading '/'. If we did not
|
||||
// strip 1 or more leading '/'s here, we would end up redirecting
|
||||
// clients to e.g. //example.com (a schema-less URL that points to
|
||||
// another site). See https://github.com/tailscale/corp/issues/16268.
|
||||
s.pathPrefix = strings.TrimLeft(path.Clean(opts.PathPrefix), "/\\")
|
||||
}
|
||||
if s.mode == ManageServerMode {
|
||||
if opts.NewAuthURL == nil {
|
||||
@@ -450,11 +449,10 @@ type authResponse struct {
|
||||
// viewerIdentity is the Tailscale identity of the source node
|
||||
// connected to this web client.
|
||||
type viewerIdentity struct {
|
||||
LoginName string `json:"loginName"`
|
||||
NodeName string `json:"nodeName"`
|
||||
NodeIP string `json:"nodeIP"`
|
||||
ProfilePicURL string `json:"profilePicUrl,omitempty"`
|
||||
Capabilities peerCapabilities `json:"capabilities"` // features peer is allowed to edit
|
||||
LoginName string `json:"loginName"`
|
||||
NodeName string `json:"nodeName"`
|
||||
NodeIP string `json:"nodeIP"`
|
||||
ProfilePicURL string `json:"profilePicUrl,omitempty"`
|
||||
}
|
||||
|
||||
// serverAPIAuth handles requests to the /api/auth endpoint
|
||||
@@ -465,16 +463,10 @@ func (s *Server) serveAPIAuth(w http.ResponseWriter, r *http.Request) {
|
||||
session, whois, status, sErr := s.getSession(r)
|
||||
|
||||
if whois != nil {
|
||||
caps, err := toPeerCapabilities(whois)
|
||||
if err != nil {
|
||||
http.Error(w, sErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
resp.ViewerIdentity = &viewerIdentity{
|
||||
LoginName: whois.UserProfile.LoginName,
|
||||
NodeName: whois.Node.Name,
|
||||
ProfilePicURL: whois.UserProfile.ProfilePicURL,
|
||||
Capabilities: caps,
|
||||
}
|
||||
if addrs := whois.Node.Addresses; len(addrs) > 0 {
|
||||
resp.ViewerIdentity.NodeIP = addrs[0].Addr().String()
|
||||
|
||||
@@ -450,7 +450,6 @@ func TestServeAuth(t *testing.T) {
|
||||
NodeName: remoteNode.Node.Name,
|
||||
NodeIP: remoteIP,
|
||||
ProfilePicURL: user.ProfilePicURL,
|
||||
Capabilities: peerCapabilities{},
|
||||
}
|
||||
|
||||
testControlURL := &defaultControlURL
|
||||
@@ -940,65 +939,36 @@ func TestServeAPIAuthMetricLogging(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestPathPrefix tests that the provided path prefix is normalized correctly.
|
||||
// If a leading '/' is missing, one should be added.
|
||||
// If multiple leading '/' are present, they should be collapsed to one.
|
||||
// Additionally verify that this prevents open redirects when enforcing the path prefix.
|
||||
func TestPathPrefix(t *testing.T) {
|
||||
func TestNoOffSiteRedirect(t *testing.T) {
|
||||
options := ServerOpts{
|
||||
Mode: LoginServerMode,
|
||||
// Emulate the admin using a --prefix option with leading slashes:
|
||||
PathPrefix: "//evil.example.com/goat",
|
||||
CGIMode: true,
|
||||
}
|
||||
s, err := NewServer(options)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
prefix string
|
||||
wantPrefix string
|
||||
target string
|
||||
wantHandled bool
|
||||
wantLocation string
|
||||
}{
|
||||
{
|
||||
name: "no-leading-slash",
|
||||
prefix: "javascript:alert(1)",
|
||||
wantPrefix: "/javascript:alert(1)",
|
||||
wantLocation: "/javascript:alert(1)/",
|
||||
},
|
||||
{
|
||||
name: "2-slashes",
|
||||
prefix: "//evil.example.com/goat",
|
||||
target: "http://localhost//evil.example.com/goat",
|
||||
// We must also get the trailing slash added:
|
||||
wantPrefix: "/evil.example.com/goat",
|
||||
wantLocation: "/evil.example.com/goat/",
|
||||
},
|
||||
{
|
||||
name: "absolute-url",
|
||||
prefix: "http://evil.example.com",
|
||||
// We must also get the trailing slash added:
|
||||
wantPrefix: "/http:/evil.example.com",
|
||||
wantLocation: "/http:/evil.example.com/",
|
||||
},
|
||||
{
|
||||
name: "double-dot",
|
||||
prefix: "/../.././etc/passwd",
|
||||
// We must also get the trailing slash added:
|
||||
wantPrefix: "/etc/passwd",
|
||||
wantLocation: "/etc/passwd/",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
options := ServerOpts{
|
||||
Mode: LoginServerMode,
|
||||
PathPrefix: tt.prefix,
|
||||
CGIMode: true,
|
||||
}
|
||||
s, err := NewServer(options)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// verify provided prefix was normalized correctly
|
||||
if s.pathPrefix != tt.wantPrefix {
|
||||
t.Errorf("prefix was not normalized correctly; want=%q, got=%q", tt.wantPrefix, s.pathPrefix)
|
||||
}
|
||||
|
||||
s.logf = t.Logf
|
||||
r := httptest.NewRequest(httpm.GET, "http://localhost/", nil)
|
||||
r := httptest.NewRequest(httpm.GET, tt.target, nil)
|
||||
w := httptest.NewRecorder()
|
||||
s.ServeHTTP(w, r)
|
||||
res := w.Result()
|
||||
@@ -1006,7 +976,7 @@ func TestPathPrefix(t *testing.T) {
|
||||
|
||||
location := w.Header().Get("Location")
|
||||
if location != tt.wantLocation {
|
||||
t.Errorf("request got wrong location; want=%q, got=%q", tt.wantLocation, location)
|
||||
t.Errorf("request(%q) got wrong location; want=%q, got=%q", tt.target, tt.wantLocation, location)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1098,163 +1068,6 @@ func TestRequireTailscaleIP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeerCapabilities(t *testing.T) {
|
||||
// Testing web.toPeerCapabilities
|
||||
toPeerCapsTests := []struct {
|
||||
name string
|
||||
whois *apitype.WhoIsResponse
|
||||
wantCaps peerCapabilities
|
||||
}{
|
||||
{
|
||||
name: "empty-whois",
|
||||
whois: nil,
|
||||
wantCaps: peerCapabilities{},
|
||||
},
|
||||
{
|
||||
name: "no-webui-caps",
|
||||
whois: &apitype.WhoIsResponse{
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityDebugPeer: []tailcfg.RawMessage{},
|
||||
},
|
||||
},
|
||||
wantCaps: peerCapabilities{},
|
||||
},
|
||||
{
|
||||
name: "one-webui-cap",
|
||||
whois: &apitype.WhoIsResponse{
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityWebUI: []tailcfg.RawMessage{
|
||||
"{\"canEdit\":[\"ssh\",\"subnet\"]}",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaps: peerCapabilities{
|
||||
capFeatureSSH: true,
|
||||
capFeatureSubnet: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple-webui-cap",
|
||||
whois: &apitype.WhoIsResponse{
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityWebUI: []tailcfg.RawMessage{
|
||||
"{\"canEdit\":[\"ssh\",\"subnet\"]}",
|
||||
"{\"canEdit\":[\"subnet\",\"exitnode\",\"*\"]}",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaps: peerCapabilities{
|
||||
capFeatureSSH: true,
|
||||
capFeatureSubnet: true,
|
||||
capFeatureExitNode: true,
|
||||
capFeatureAll: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "case=insensitive-caps",
|
||||
whois: &apitype.WhoIsResponse{
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityWebUI: []tailcfg.RawMessage{
|
||||
"{\"canEdit\":[\"SSH\",\"sUBnet\"]}",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaps: peerCapabilities{
|
||||
capFeatureSSH: true,
|
||||
capFeatureSubnet: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "random-canEdit-contents-dont-error",
|
||||
whois: &apitype.WhoIsResponse{
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityWebUI: []tailcfg.RawMessage{
|
||||
"{\"canEdit\":[\"unknown-feature\"]}",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaps: peerCapabilities{
|
||||
"unknown-feature": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no-canEdit-section",
|
||||
whois: &apitype.WhoIsResponse{
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityWebUI: []tailcfg.RawMessage{
|
||||
"{\"canDoSomething\":[\"*\"]}",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaps: peerCapabilities{},
|
||||
},
|
||||
}
|
||||
for _, tt := range toPeerCapsTests {
|
||||
t.Run("toPeerCapabilities-"+tt.name, func(t *testing.T) {
|
||||
got, err := toPeerCapabilities(tt.whois)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(got, tt.wantCaps); diff != "" {
|
||||
t.Errorf("wrong caps; (-got+want):%v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Testing web.peerCapabilities.canEdit
|
||||
canEditTests := []struct {
|
||||
name string
|
||||
caps peerCapabilities
|
||||
wantCanEdit map[capFeature]bool
|
||||
}{
|
||||
{
|
||||
name: "empty-caps",
|
||||
caps: nil,
|
||||
wantCanEdit: map[capFeature]bool{
|
||||
capFeatureAll: false,
|
||||
capFeatureFunnel: false,
|
||||
capFeatureSSH: false,
|
||||
capFeatureSubnet: false,
|
||||
capFeatureExitNode: false,
|
||||
capFeatureAccount: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "some-caps",
|
||||
caps: peerCapabilities{capFeatureSSH: true, capFeatureAccount: true},
|
||||
wantCanEdit: map[capFeature]bool{
|
||||
capFeatureAll: false,
|
||||
capFeatureFunnel: false,
|
||||
capFeatureSSH: true,
|
||||
capFeatureSubnet: false,
|
||||
capFeatureExitNode: false,
|
||||
capFeatureAccount: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wildcard-in-caps",
|
||||
caps: peerCapabilities{capFeatureAll: true, capFeatureAccount: true},
|
||||
wantCanEdit: map[capFeature]bool{
|
||||
capFeatureAll: true,
|
||||
capFeatureFunnel: true,
|
||||
capFeatureSSH: true,
|
||||
capFeatureSubnet: true,
|
||||
capFeatureExitNode: true,
|
||||
capFeatureAccount: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range canEditTests {
|
||||
t.Run("canEdit-"+tt.name, func(t *testing.T) {
|
||||
for f, want := range tt.wantCanEdit {
|
||||
if got := tt.caps.canEdit(f); got != want {
|
||||
t.Errorf("wrong canEdit(%s); got=%v, want=%v", f, got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
defaultControlURL = "https://controlplane.tailscale.com"
|
||||
testAuthPath = "/a/12345"
|
||||
|
||||
@@ -48,13 +48,6 @@
|
||||
// ${TS_CERT_DOMAIN}, it will be replaced with the value of the available FQDN.
|
||||
// It cannot be used in conjunction with TS_DEST_IP. The file is watched for changes,
|
||||
// and will be re-applied when it changes.
|
||||
// - EXPERIMENTAL_TS_CONFIGFILE_PATH: if specified, a path to tailscaled
|
||||
// config. If this is set, TS_HOSTNAME, TS_EXTRA_ARGS, TS_AUTHKEY,
|
||||
// TS_ROUTES, TS_ACCEPT_DNS env vars must not be set. If this is set,
|
||||
// containerboot only runs `tailscaled --config <path-to-this-configfile>`
|
||||
// and not `tailscale up` or `tailscale set`.
|
||||
// The config file contents are currently read once on container start.
|
||||
// NB: This env var is currently experimental and the logic will likely change!
|
||||
//
|
||||
// When running on Kubernetes, containerboot defaults to storing state in the
|
||||
// "tailscale" kube secret. To store state on local disk instead, set
|
||||
@@ -90,7 +83,6 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/conffile"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/ptr"
|
||||
@@ -110,30 +102,40 @@ func main() {
|
||||
tailscale.I_Acknowledge_This_API_Is_Unstable = true
|
||||
|
||||
cfg := &settings{
|
||||
AuthKey: defaultEnvs([]string{"TS_AUTHKEY", "TS_AUTH_KEY"}, ""),
|
||||
Hostname: defaultEnv("TS_HOSTNAME", ""),
|
||||
Routes: defaultEnvStringPointer("TS_ROUTES"),
|
||||
ServeConfigPath: defaultEnv("TS_SERVE_CONFIG", ""),
|
||||
ProxyTo: defaultEnv("TS_DEST_IP", ""),
|
||||
TailnetTargetIP: defaultEnv("TS_TAILNET_TARGET_IP", ""),
|
||||
TailnetTargetFQDN: defaultEnv("TS_TAILNET_TARGET_FQDN", ""),
|
||||
DaemonExtraArgs: defaultEnv("TS_TAILSCALED_EXTRA_ARGS", ""),
|
||||
ExtraArgs: defaultEnv("TS_EXTRA_ARGS", ""),
|
||||
InKubernetes: os.Getenv("KUBERNETES_SERVICE_HOST") != "",
|
||||
UserspaceMode: defaultBool("TS_USERSPACE", true),
|
||||
StateDir: defaultEnv("TS_STATE_DIR", ""),
|
||||
AcceptDNS: defaultEnvBoolPointer("TS_ACCEPT_DNS"),
|
||||
WebUI: defaultEnvBoolPointer("TS_WEBUI"),
|
||||
KubeSecret: defaultEnv("TS_KUBE_SECRET", "tailscale"),
|
||||
SOCKSProxyAddr: defaultEnv("TS_SOCKS5_SERVER", ""),
|
||||
HTTPProxyAddr: defaultEnv("TS_OUTBOUND_HTTP_PROXY_LISTEN", ""),
|
||||
Socket: defaultEnv("TS_SOCKET", "/tmp/tailscaled.sock"),
|
||||
AuthOnce: defaultBool("TS_AUTH_ONCE", false),
|
||||
Root: defaultEnv("TS_TEST_ONLY_ROOT", "/"),
|
||||
TailscaledConfigFilePath: defaultEnv("EXPERIMENTAL_TS_CONFIGFILE_PATH", ""),
|
||||
AuthKey: defaultEnvs([]string{"TS_AUTHKEY", "TS_AUTH_KEY"}, ""),
|
||||
Hostname: defaultEnv("TS_HOSTNAME", ""),
|
||||
Routes: defaultEnvPointer("TS_ROUTES"),
|
||||
ServeConfigPath: defaultEnv("TS_SERVE_CONFIG", ""),
|
||||
ConfigFilePath: defaultEnv("TS_CONFIGFILE_PATH", ""),
|
||||
ProxyTo: defaultEnv("TS_DEST_IP", ""),
|
||||
TailnetTargetIP: defaultEnv("TS_TAILNET_TARGET_IP", ""),
|
||||
TailnetTargetFQDN: defaultEnv("TS_TAILNET_TARGET_FQDN", ""),
|
||||
DaemonExtraArgs: defaultEnv("TS_TAILSCALED_EXTRA_ARGS", ""),
|
||||
ExtraArgs: defaultEnv("TS_EXTRA_ARGS", ""),
|
||||
InKubernetes: os.Getenv("KUBERNETES_SERVICE_HOST") != "",
|
||||
UserspaceMode: defaultBool("TS_USERSPACE", true),
|
||||
StateDir: defaultEnv("TS_STATE_DIR", ""),
|
||||
AcceptDNS: defaultBool("TS_ACCEPT_DNS", false),
|
||||
KubeSecret: defaultEnv("TS_KUBE_SECRET", "tailscale"),
|
||||
SOCKSProxyAddr: defaultEnv("TS_SOCKS5_SERVER", ""),
|
||||
HTTPProxyAddr: defaultEnv("TS_OUTBOUND_HTTP_PROXY_LISTEN", ""),
|
||||
Socket: defaultEnv("TS_SOCKET", "/tmp/tailscaled.sock"),
|
||||
AuthOnce: defaultBool("TS_AUTH_ONCE", false),
|
||||
Root: defaultEnv("TS_TEST_ONLY_ROOT", "/"),
|
||||
}
|
||||
if err := cfg.validate(); err != nil {
|
||||
log.Fatalf("invalid configuration: %v", err)
|
||||
|
||||
if cfg.ProxyTo != "" && cfg.UserspaceMode {
|
||||
log.Fatal("TS_DEST_IP is not supported with TS_USERSPACE")
|
||||
}
|
||||
|
||||
if cfg.TailnetTargetIP != "" && cfg.UserspaceMode {
|
||||
log.Fatal("TS_TAILNET_TARGET_IP is not supported with TS_USERSPACE")
|
||||
}
|
||||
if cfg.TailnetTargetFQDN != "" && cfg.UserspaceMode {
|
||||
log.Fatal("TS_TAILNET_TARGET_FQDN is not supported with TS_USERSPACE")
|
||||
}
|
||||
if cfg.TailnetTargetFQDN != "" && cfg.TailnetTargetIP != "" {
|
||||
log.Fatal("Both TS_TAILNET_TARGET_IP and TS_TAILNET_FQDN cannot be set")
|
||||
}
|
||||
|
||||
if !cfg.UserspaceMode {
|
||||
@@ -170,7 +172,7 @@ func main() {
|
||||
}
|
||||
cfg.KubernetesCanPatch = canPatch
|
||||
|
||||
if cfg.AuthKey == "" && !isOneStepConfig(cfg) {
|
||||
if cfg.AuthKey == "" {
|
||||
key, err := findKeyInKubeSecret(bootCtx, cfg.KubeSecret)
|
||||
if err != nil {
|
||||
log.Fatalf("Getting authkey from kube secret: %v", err)
|
||||
@@ -252,7 +254,7 @@ func main() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if isTwoStepConfigAlwaysAuth(cfg) {
|
||||
if !cfg.AuthOnce {
|
||||
if err := authTailscale(); err != nil {
|
||||
log.Fatalf("failed to auth tailscale: %v", err)
|
||||
}
|
||||
@@ -268,13 +270,6 @@ authLoop:
|
||||
if n.State != nil {
|
||||
switch *n.State {
|
||||
case ipn.NeedsLogin:
|
||||
if isOneStepConfig(cfg) {
|
||||
// This could happen if this is the
|
||||
// first time tailscaled was run for
|
||||
// this device and the auth key was not
|
||||
// passed via the configfile.
|
||||
log.Fatalf("invalid state: tailscaled daemon started with a config file, but tailscale is not logged in: ensure you pass a valid auth key in the config file.")
|
||||
}
|
||||
if err := authTailscale(); err != nil {
|
||||
log.Fatalf("failed to auth tailscale: %v", err)
|
||||
}
|
||||
@@ -299,13 +294,13 @@ authLoop:
|
||||
ctx, cancel := contextWithExitSignalWatch()
|
||||
defer cancel()
|
||||
|
||||
if isTwoStepConfigAuthOnce(cfg) {
|
||||
// Now that we are authenticated, we can set/reset any of the
|
||||
// settings that we need to.
|
||||
if err := tailscaleSet(ctx, cfg); err != nil {
|
||||
log.Fatalf("failed to auth tailscale: %v", err)
|
||||
}
|
||||
}
|
||||
// if cfg.AuthOnce {
|
||||
// // Now that we are authenticated, we can set/reset any of the
|
||||
// // settings that we need to.
|
||||
// // if err := tailscaleSet(ctx, cfg); err != nil {
|
||||
// // log.Fatalf("failed to auth tailscale: %v", err)
|
||||
// // }
|
||||
// }
|
||||
|
||||
if cfg.ServeConfigPath != "" {
|
||||
// Remove any serve config that may have been set by a previous run of
|
||||
@@ -315,14 +310,14 @@ authLoop:
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.InKubernetes && cfg.KubeSecret != "" && cfg.KubernetesCanPatch && isTwoStepConfigAuthOnce(cfg) {
|
||||
if cfg.InKubernetes && cfg.KubeSecret != "" && cfg.KubernetesCanPatch && cfg.AuthOnce {
|
||||
// We were told to only auth once, so any secret-bound
|
||||
// authkey is no longer needed. We don't strictly need to
|
||||
// wipe it, but it's good hygiene.
|
||||
log.Printf("Deleting authkey from kube secret")
|
||||
if err := deleteAuthKey(ctx, cfg.KubeSecret); err != nil {
|
||||
log.Fatalf("deleting authkey from kube secret: %v", err)
|
||||
}
|
||||
// log.Printf("Deleting authkey from kube secret")
|
||||
// if err := deleteAuthKey(ctx, cfg.KubeSecret); err != nil {
|
||||
// log.Fatalf("deleting authkey from kube secret: %v", err)
|
||||
// }
|
||||
}
|
||||
|
||||
w, err = client.WatchIPNBus(ctx, ipn.NotifyInitialNetMap|ipn.NotifyInitialState)
|
||||
@@ -640,46 +635,52 @@ func tailscaledArgs(cfg *settings) []string {
|
||||
if cfg.HTTPProxyAddr != "" {
|
||||
args = append(args, "--outbound-http-proxy-listen="+cfg.HTTPProxyAddr)
|
||||
}
|
||||
if cfg.TailscaledConfigFilePath != "" {
|
||||
args = append(args, "--config="+cfg.TailscaledConfigFilePath)
|
||||
}
|
||||
if cfg.DaemonExtraArgs != "" {
|
||||
args = append(args, strings.Fields(cfg.DaemonExtraArgs)...)
|
||||
}
|
||||
if cfg.ConfigFilePath != "" {
|
||||
args = append(args, "--config="+cfg.ConfigFilePath)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// tailscaleUp uses cfg to run 'tailscale up' everytime containerboot starts, or
|
||||
// if TS_AUTH_ONCE is set, only the first time containerboot starts.
|
||||
func tailscaleUp(ctx context.Context, cfg *settings) error {
|
||||
args := []string{"--socket=" + cfg.Socket, "up"}
|
||||
if cfg.AcceptDNS != nil && *cfg.AcceptDNS {
|
||||
args = append(args, "--accept-dns=true")
|
||||
} else {
|
||||
args = append(args, "--accept-dns=false")
|
||||
command := "up"
|
||||
if cfg.ConfigFilePath != "" {
|
||||
command = "login"
|
||||
}
|
||||
args := []string{"--socket=" + cfg.Socket, command}
|
||||
if cfg.AuthKey != "" {
|
||||
args = append(args, "--authkey="+cfg.AuthKey)
|
||||
}
|
||||
// --advertise-routes can be passed an empty string to configure a
|
||||
// device (that might have previously advertised subnet routes) to not
|
||||
// advertise any routes. Respect an empty string passed by a user and
|
||||
// use it to explicitly unset the routes.
|
||||
if cfg.Routes != nil {
|
||||
args = append(args, "--advertise-routes="+*cfg.Routes)
|
||||
if cfg.ConfigFilePath == "" {
|
||||
if cfg.AcceptDNS {
|
||||
args = append(args, "--accept-dns=true")
|
||||
} else {
|
||||
args = append(args, "--accept-dns=false")
|
||||
}
|
||||
// --advertise-routes can be passed an empty string to configure a
|
||||
// device (that might have previously advertised subnet routes) to not
|
||||
// advertise any routes. Respect an empty string passed by a user and
|
||||
// use it to explicitly unset the routes.
|
||||
if cfg.Routes != nil {
|
||||
args = append(args, "--advertise-routes="+*cfg.Routes)
|
||||
}
|
||||
if cfg.Hostname != "" {
|
||||
args = append(args, "--hostname="+cfg.Hostname)
|
||||
}
|
||||
if cfg.ExtraArgs != "" {
|
||||
args = append(args, strings.Fields(cfg.ExtraArgs)...)
|
||||
}
|
||||
}
|
||||
if cfg.Hostname != "" {
|
||||
args = append(args, "--hostname="+cfg.Hostname)
|
||||
}
|
||||
if cfg.ExtraArgs != "" {
|
||||
args = append(args, strings.Fields(cfg.ExtraArgs)...)
|
||||
}
|
||||
log.Printf("Running 'tailscale up'")
|
||||
log.Printf("Running 'tailscale %s' with args %#+v", command, args)
|
||||
cmd := exec.CommandContext(ctx, "tailscale", args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("tailscale up failed: %v", err)
|
||||
log.Printf("tailscale up with args %#+v failed: %v", args, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -687,37 +688,35 @@ func tailscaleUp(ctx context.Context, cfg *settings) error {
|
||||
// tailscaleSet uses cfg to run 'tailscale set' to set any known configuration
|
||||
// options that are passed in via environment variables. This is run after the
|
||||
// node is in Running state and only if TS_AUTH_ONCE is set.
|
||||
func tailscaleSet(ctx context.Context, cfg *settings) error {
|
||||
args := []string{"--socket=" + cfg.Socket, "set"}
|
||||
if cfg.AcceptDNS != nil && *cfg.AcceptDNS {
|
||||
args = append(args, "--accept-dns=true")
|
||||
} else {
|
||||
args = append(args, "--accept-dns=false")
|
||||
}
|
||||
// --advertise-routes can be passed an empty string to configure a
|
||||
// device (that might have previously advertised subnet routes) to not
|
||||
// advertise any routes. Respect an empty string passed by a user and
|
||||
// use it to explicitly unset the routes.
|
||||
if cfg.Routes != nil {
|
||||
args = append(args, "--advertise-routes="+*cfg.Routes)
|
||||
}
|
||||
if cfg.Hostname != "" {
|
||||
args = append(args, "--hostname="+cfg.Hostname)
|
||||
}
|
||||
if cfg.WebUI != nil && *cfg.WebUI {
|
||||
args = append(args, "--webclient=true")
|
||||
} else {
|
||||
args = append(args, "--webclient=false")
|
||||
}
|
||||
log.Printf("Running 'tailscale set'")
|
||||
cmd := exec.CommandContext(ctx, "tailscale", args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("tailscale set failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// func tailscaleSet(ctx context.Context, cfg *settings) error {
|
||||
// args := []string{"--socket=" + cfg.Socket, "set"}
|
||||
// // TODO: fix
|
||||
// if cfg.ConfigFilePath == "" {
|
||||
// if cfg.AcceptDNS {
|
||||
// args = append(args, "--accept-dns=true")
|
||||
// } else {
|
||||
// args = append(args, "--accept-dns=false")
|
||||
// }
|
||||
// // --advertise-routes can be passed an empty string to configure a
|
||||
// // device (that might have previously advertised subnet routes) to not
|
||||
// // advertise any routes. Respect an empty string passed by a user and
|
||||
// // use it to explicitly unset the routes.
|
||||
// if cfg.Routes != nil {
|
||||
// args = append(args, "--advertise-routes="+*cfg.Routes)
|
||||
// }
|
||||
// if cfg.Hostname != "" {
|
||||
// args = append(args, "--hostname="+cfg.Hostname)
|
||||
// }
|
||||
// }
|
||||
// log.Printf("Running 'tailscale set'")
|
||||
// cmd := exec.CommandContext(ctx, "tailscale", args...)
|
||||
// cmd.Stdout = os.Stdout
|
||||
// cmd.Stderr = os.Stderr
|
||||
// if err := cmd.Run(); err != nil {
|
||||
// return fmt.Errorf("tailscale set failed: %v", err)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// ensureTunFile checks that /dev/net/tun exists, creating it if
|
||||
// missing.
|
||||
@@ -887,47 +886,22 @@ type settings struct {
|
||||
// TailnetTargetFQDN is an MagicDNS name to which all incoming
|
||||
// non-Tailscale traffic should be proxied. This must be a full Tailnet
|
||||
// node FQDN.
|
||||
TailnetTargetFQDN string
|
||||
ServeConfigPath string
|
||||
DaemonExtraArgs string
|
||||
ExtraArgs string
|
||||
InKubernetes bool
|
||||
UserspaceMode bool
|
||||
StateDir string
|
||||
AcceptDNS *bool
|
||||
WebUI *bool
|
||||
KubeSecret string
|
||||
SOCKSProxyAddr string
|
||||
HTTPProxyAddr string
|
||||
Socket string
|
||||
AuthOnce bool
|
||||
Root string
|
||||
KubernetesCanPatch bool
|
||||
TailscaledConfigFilePath string
|
||||
}
|
||||
|
||||
func (s *settings) validate() error {
|
||||
if s.TailscaledConfigFilePath != "" {
|
||||
if _, err := conffile.Load(s.TailscaledConfigFilePath); err != nil {
|
||||
return fmt.Errorf("error validating tailscaled configfile contents: %w", err)
|
||||
}
|
||||
}
|
||||
if s.ProxyTo != "" && s.UserspaceMode {
|
||||
return errors.New("TS_DEST_IP is not supported with TS_USERSPACE")
|
||||
}
|
||||
if s.TailnetTargetIP != "" && s.UserspaceMode {
|
||||
return errors.New("TS_TAILNET_TARGET_IP is not supported with TS_USERSPACE")
|
||||
}
|
||||
if s.TailnetTargetFQDN != "" && s.UserspaceMode {
|
||||
return errors.New("TS_TAILNET_TARGET_FQDN is not supported with TS_USERSPACE")
|
||||
}
|
||||
if s.TailnetTargetFQDN != "" && s.TailnetTargetIP != "" {
|
||||
return errors.New("Both TS_TAILNET_TARGET_IP and TS_TAILNET_FQDN cannot be set")
|
||||
}
|
||||
if s.TailscaledConfigFilePath != "" && (s.AcceptDNS != nil || s.AuthKey != "" || s.Routes != nil || s.ExtraArgs != "" || s.Hostname != "") {
|
||||
return errors.New("EXPERIMENTAL_TS_CONFIGFILE_PATH cannot be set in combination with TS_HOSTNAME, TS_EXTRA_ARGS, TS_AUTHKEY, TS_ROUTES, TS_ACCEPT_DNS.")
|
||||
}
|
||||
return nil
|
||||
TailnetTargetFQDN string
|
||||
ServeConfigPath string
|
||||
DaemonExtraArgs string
|
||||
ExtraArgs string
|
||||
InKubernetes bool
|
||||
UserspaceMode bool
|
||||
StateDir string
|
||||
AcceptDNS bool
|
||||
KubeSecret string
|
||||
SOCKSProxyAddr string
|
||||
HTTPProxyAddr string
|
||||
Socket string
|
||||
AuthOnce bool
|
||||
Root string
|
||||
KubernetesCanPatch bool
|
||||
ConfigFilePath string
|
||||
}
|
||||
|
||||
// defaultEnv returns the value of the given envvar name, or defVal if
|
||||
@@ -939,28 +913,16 @@ func defaultEnv(name, defVal string) string {
|
||||
return defVal
|
||||
}
|
||||
|
||||
// defaultEnvStringPointer returns a pointer to the given envvar value if set, else
|
||||
// defaultEnvPointer returns a pointer to the given envvar value if set, else
|
||||
// returns nil. This is useful in cases where we need to distinguish between a
|
||||
// variable being set to empty string vs unset.
|
||||
func defaultEnvStringPointer(name string) *string {
|
||||
func defaultEnvPointer(name string) *string {
|
||||
if v, ok := os.LookupEnv(name); ok {
|
||||
return &v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// defaultEnvBoolPointer returns a pointer to the given envvar value if set, else
|
||||
// returns nil. This is useful in cases where we need to distinguish between a
|
||||
// variable being explicitly set to false vs unset.
|
||||
func defaultEnvBoolPointer(name string) *bool {
|
||||
v := os.Getenv(name)
|
||||
ret, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
func defaultEnvs(names []string, defVal string) string {
|
||||
for _, name := range names {
|
||||
if v, ok := os.LookupEnv(name); ok {
|
||||
@@ -1002,27 +964,3 @@ func contextWithExitSignalWatch() (context.Context, func()) {
|
||||
}
|
||||
return ctx, f
|
||||
}
|
||||
|
||||
// isTwoStepConfigAuthOnce returns true if the Tailscale node should be configured
|
||||
// in two steps and login should only happen once.
|
||||
// Step 1: run 'tailscaled'
|
||||
// Step 2):
|
||||
// A) if this is the first time starting this node run 'tailscale up --authkey <authkey> <config opts>'
|
||||
// B) if this is not the first time starting this node run 'tailscale set <config opts>'.
|
||||
func isTwoStepConfigAuthOnce(cfg *settings) bool {
|
||||
return cfg.AuthOnce && cfg.TailscaledConfigFilePath == ""
|
||||
}
|
||||
|
||||
// isTwoStepConfigAlwaysAuth returns true if the Tailscale node should be configured
|
||||
// in two steps and we should log in every time it starts.
|
||||
// Step 1: run 'tailscaled'
|
||||
// Step 2): run 'tailscale up --authkey <authkey> <config opts>'
|
||||
func isTwoStepConfigAlwaysAuth(cfg *settings) bool {
|
||||
return !cfg.AuthOnce && cfg.TailscaledConfigFilePath == ""
|
||||
}
|
||||
|
||||
// isOneStepConfig returns true if the Tailscale node should always be ran and
|
||||
// configured in a single step by running 'tailscaled <config opts>'
|
||||
func isOneStepConfig(cfg *settings) bool {
|
||||
return cfg.TailscaledConfigFilePath != ""
|
||||
}
|
||||
|
||||
@@ -52,12 +52,6 @@ func TestContainerBoot(t *testing.T) {
|
||||
}
|
||||
defer kube.Close()
|
||||
|
||||
tailscaledConf := &ipn.ConfigVAlpha{AuthKey: func(s string) *string { return &s }("foo"), Version: "alpha0"}
|
||||
tailscaledConfBytes, err := json.Marshal(tailscaledConf)
|
||||
if err != nil {
|
||||
t.Fatalf("error unmarshaling tailscaled config: %v", err)
|
||||
}
|
||||
|
||||
dirs := []string{
|
||||
"var/lib",
|
||||
"usr/bin",
|
||||
@@ -65,7 +59,6 @@ func TestContainerBoot(t *testing.T) {
|
||||
"dev/net",
|
||||
"proc/sys/net/ipv4",
|
||||
"proc/sys/net/ipv6/conf/all",
|
||||
"etc",
|
||||
}
|
||||
for _, path := range dirs {
|
||||
if err := os.MkdirAll(filepath.Join(d, path), 0700); err != nil {
|
||||
@@ -80,7 +73,6 @@ func TestContainerBoot(t *testing.T) {
|
||||
"dev/net/tun": []byte(""),
|
||||
"proc/sys/net/ipv4/ip_forward": []byte("0"),
|
||||
"proc/sys/net/ipv6/conf/all/forwarding": []byte("0"),
|
||||
"etc/tailscaled": tailscaledConfBytes,
|
||||
}
|
||||
resetFiles := func() {
|
||||
for path, content := range files {
|
||||
@@ -318,7 +310,7 @@ func TestContainerBoot(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ingress proxy",
|
||||
Name: "ingres proxy",
|
||||
Env: map[string]string{
|
||||
"TS_AUTHKEY": "tskey-key",
|
||||
"TS_DEST_IP": "1.2.3.4",
|
||||
@@ -637,21 +629,6 @@ func TestContainerBoot(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "experimental tailscaled configfile",
|
||||
Env: map[string]string{
|
||||
"EXPERIMENTAL_TS_CONFIGFILE_PATH": filepath.Join(d, "etc/tailscaled"),
|
||||
},
|
||||
Phases: []phase{
|
||||
{
|
||||
WantCmds: []string{
|
||||
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking --config=/etc/tailscaled",
|
||||
},
|
||||
}, {
|
||||
Notify: runningNotify,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -11,6 +11,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
W 💣 github.com/dblohm7/wingoes from tailscale.com/util/winutil
|
||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||
github.com/golang/protobuf/proto from github.com/matttproud/golang_protobuf_extensions/pbutil
|
||||
L github.com/google/nftables from tailscale.com/util/linuxfw
|
||||
L 💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
|
||||
L 💣 github.com/google/nftables/binaryutil from github.com/google/nftables+
|
||||
@@ -22,6 +23,8 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
|
||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||
github.com/klauspost/compress/flate from nhooyr.io/websocket
|
||||
github.com/matttproud/golang_protobuf_extensions/pbutil from github.com/prometheus/common/expfmt
|
||||
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
|
||||
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
|
||||
L github.com/mdlayher/netlink/nltest from github.com/google/nftables
|
||||
@@ -48,9 +51,8 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
💣 go4.org/mem from tailscale.com/client/tailscale+
|
||||
go4.org/netipx from tailscale.com/wgengine/filter+
|
||||
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
|
||||
google.golang.org/protobuf/encoding/protodelim from github.com/prometheus/common/expfmt
|
||||
google.golang.org/protobuf/encoding/prototext from github.com/prometheus/common/expfmt+
|
||||
google.golang.org/protobuf/encoding/protowire from google.golang.org/protobuf/encoding/protodelim+
|
||||
google.golang.org/protobuf/encoding/prototext from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/encoding/protowire from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/internal/descfmt from google.golang.org/protobuf/internal/filedesc
|
||||
google.golang.org/protobuf/internal/descopts from google.golang.org/protobuf/internal/filedesc+
|
||||
google.golang.org/protobuf/internal/detrand from google.golang.org/protobuf/internal/descfmt+
|
||||
@@ -69,15 +71,16 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
google.golang.org/protobuf/internal/set from google.golang.org/protobuf/encoding/prototext
|
||||
💣 google.golang.org/protobuf/internal/strs from google.golang.org/protobuf/encoding/prototext+
|
||||
google.golang.org/protobuf/internal/version from google.golang.org/protobuf/runtime/protoimpl
|
||||
google.golang.org/protobuf/proto from github.com/prometheus/client_golang/prometheus+
|
||||
💣 google.golang.org/protobuf/reflect/protoreflect from github.com/prometheus/client_model/go+
|
||||
google.golang.org/protobuf/reflect/protoregistry from google.golang.org/protobuf/encoding/prototext+
|
||||
google.golang.org/protobuf/runtime/protoiface from google.golang.org/protobuf/internal/impl+
|
||||
google.golang.org/protobuf/runtime/protoimpl from github.com/prometheus/client_model/go+
|
||||
google.golang.org/protobuf/proto from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/reflect/protodesc from github.com/golang/protobuf/proto
|
||||
💣 google.golang.org/protobuf/reflect/protoreflect from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/reflect/protoregistry from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/runtime/protoiface from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/runtime/protoimpl from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/types/descriptorpb from google.golang.org/protobuf/reflect/protodesc
|
||||
google.golang.org/protobuf/types/known/timestamppb from github.com/prometheus/client_golang/prometheus+
|
||||
nhooyr.io/websocket from tailscale.com/cmd/derper+
|
||||
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
|
||||
nhooyr.io/websocket/internal/util from nhooyr.io/websocket
|
||||
nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
|
||||
tailscale.com from tailscale.com/version
|
||||
tailscale.com/atomicfile from tailscale.com/cmd/derper+
|
||||
@@ -102,8 +105,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
tailscale.com/net/netutil from tailscale.com/client/tailscale
|
||||
tailscale.com/net/packet from tailscale.com/wgengine/filter
|
||||
tailscale.com/net/sockstats from tailscale.com/derp/derphttp
|
||||
tailscale.com/net/stun from tailscale.com/net/stunserver
|
||||
tailscale.com/net/stunserver from tailscale.com/cmd/derper
|
||||
tailscale.com/net/stun from tailscale.com/cmd/derper
|
||||
L tailscale.com/net/tcpinfo from tailscale.com/derp
|
||||
tailscale.com/net/tlsdial from tailscale.com/derp/derphttp
|
||||
tailscale.com/net/tsaddr from tailscale.com/ipn+
|
||||
@@ -139,7 +141,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
tailscale.com/util/cloudenv from tailscale.com/hostinfo+
|
||||
W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy
|
||||
tailscale.com/util/cmpx from tailscale.com/cmd/derper+
|
||||
tailscale.com/util/ctxkey from tailscale.com/tsweb+
|
||||
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
|
||||
tailscale.com/util/dnsname from tailscale.com/hostinfo+
|
||||
tailscale.com/util/httpm from tailscale.com/client/tailscale
|
||||
@@ -151,7 +152,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
tailscale.com/util/set from tailscale.com/health+
|
||||
tailscale.com/util/singleflight from tailscale.com/net/dnscache
|
||||
tailscale.com/util/slicesx from tailscale.com/cmd/derper+
|
||||
tailscale.com/util/syspolicy from tailscale.com/ipn
|
||||
tailscale.com/util/vizerror from tailscale.com/tsweb+
|
||||
W 💣 tailscale.com/util/winutil from tailscale.com/hostinfo+
|
||||
tailscale.com/version from tailscale.com/derp+
|
||||
@@ -231,7 +231,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
encoding/pem from crypto/tls+
|
||||
errors from bufio+
|
||||
expvar from tailscale.com/cmd/derper+
|
||||
flag from tailscale.com/cmd/derper+
|
||||
flag from tailscale.com/cmd/derper
|
||||
fmt from compress/flate+
|
||||
go/token from google.golang.org/protobuf/internal/strs
|
||||
hash from crypto+
|
||||
@@ -262,7 +262,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
net/url from crypto/x509+
|
||||
os from crypto/rand+
|
||||
os/exec from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+
|
||||
os/signal from tailscale.com/cmd/derper
|
||||
W os/user from tailscale.com/util/winutil
|
||||
path from golang.org/x/crypto/acme/autocert+
|
||||
path/filepath from crypto/x509+
|
||||
@@ -272,7 +271,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
runtime/debug from golang.org/x/crypto/acme+
|
||||
runtime/metrics from github.com/prometheus/client_golang/prometheus+
|
||||
runtime/pprof from net/http/pprof
|
||||
runtime/trace from net/http/pprof+
|
||||
runtime/trace from net/http/pprof
|
||||
slices from tailscale.com/ipn/ipnstate+
|
||||
sort from compress/flate+
|
||||
strconv from compress/flate+
|
||||
@@ -280,7 +279,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
sync from compress/flate+
|
||||
sync/atomic from context+
|
||||
syscall from crypto/rand+
|
||||
testing from tailscale.com/util/syspolicy
|
||||
text/tabwriter from runtime/pprof
|
||||
time from compress/gzip+
|
||||
unicode from bytes+
|
||||
|
||||
@@ -17,12 +17,11 @@ import (
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"go4.org/mem"
|
||||
@@ -31,7 +30,7 @@ import (
|
||||
"tailscale.com/derp"
|
||||
"tailscale.com/derp/derphttp"
|
||||
"tailscale.com/metrics"
|
||||
"tailscale.com/net/stunserver"
|
||||
"tailscale.com/net/stun"
|
||||
"tailscale.com/tsweb"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/util/cmpx"
|
||||
@@ -60,11 +59,25 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
stats = new(metrics.Set)
|
||||
stunDisposition = &metrics.LabelMap{Label: "disposition"}
|
||||
stunAddrFamily = &metrics.LabelMap{Label: "family"}
|
||||
tlsRequestVersion = &metrics.LabelMap{Label: "version"}
|
||||
tlsActiveVersion = &metrics.LabelMap{Label: "version"}
|
||||
|
||||
stunReadError = stunDisposition.Get("read_error")
|
||||
stunNotSTUN = stunDisposition.Get("not_stun")
|
||||
stunWriteError = stunDisposition.Get("write_error")
|
||||
stunSuccess = stunDisposition.Get("success")
|
||||
|
||||
stunIPv4 = stunAddrFamily.Get("ipv4")
|
||||
stunIPv6 = stunAddrFamily.Get("ipv6")
|
||||
)
|
||||
|
||||
func init() {
|
||||
stats.Set("counter_requests", stunDisposition)
|
||||
stats.Set("counter_addrfamily", stunAddrFamily)
|
||||
expvar.Publish("stun", stats)
|
||||
expvar.Publish("derper_tls_request_version", tlsRequestVersion)
|
||||
expvar.Publish("gauge_derper_tls_active_version", tlsActiveVersion)
|
||||
}
|
||||
@@ -122,9 +135,6 @@ func writeNewConfig() config {
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer cancel()
|
||||
|
||||
if *dev {
|
||||
*addr = ":3340" // above the keys DERP
|
||||
log.Printf("Running in dev mode.")
|
||||
@@ -136,11 +146,6 @@ func main() {
|
||||
log.Fatalf("invalid server address: %v", err)
|
||||
}
|
||||
|
||||
if *runSTUN {
|
||||
ss := stunserver.New(ctx)
|
||||
go ss.ListenAndServe(net.JoinHostPort(listenHost, fmt.Sprint(*stunPort)))
|
||||
}
|
||||
|
||||
cfg := loadConfig()
|
||||
|
||||
serveTLS := tsweb.IsProd443(*addr) || *certMode == "manual"
|
||||
@@ -216,6 +221,10 @@ func main() {
|
||||
}))
|
||||
debug.Handle("traffic", "Traffic check", http.HandlerFunc(s.ServeDebugTraffic))
|
||||
|
||||
if *runSTUN {
|
||||
go serveSTUN(listenHost, *stunPort)
|
||||
}
|
||||
|
||||
quietLogger := log.New(logFilter{}, "", 0)
|
||||
httpsrv := &http.Server{
|
||||
Addr: *addr,
|
||||
@@ -232,10 +241,6 @@ func main() {
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
httpsrv.Shutdown(ctx)
|
||||
}()
|
||||
|
||||
if serveTLS {
|
||||
log.Printf("derper: serving on %s with TLS", *addr)
|
||||
@@ -346,6 +351,59 @@ func probeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func serveSTUN(host string, port int) {
|
||||
pc, err := net.ListenPacket("udp", net.JoinHostPort(host, fmt.Sprint(port)))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to open STUN listener: %v", err)
|
||||
}
|
||||
log.Printf("running STUN server on %v", pc.LocalAddr())
|
||||
serverSTUNListener(context.Background(), pc.(*net.UDPConn))
|
||||
}
|
||||
|
||||
func serverSTUNListener(ctx context.Context, pc *net.UDPConn) {
|
||||
var buf [64 << 10]byte
|
||||
var (
|
||||
n int
|
||||
ua *net.UDPAddr
|
||||
err error
|
||||
)
|
||||
for {
|
||||
n, ua, err = pc.ReadFromUDP(buf[:])
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
log.Printf("STUN ReadFrom: %v", err)
|
||||
time.Sleep(time.Second)
|
||||
stunReadError.Add(1)
|
||||
continue
|
||||
}
|
||||
pkt := buf[:n]
|
||||
if !stun.Is(pkt) {
|
||||
stunNotSTUN.Add(1)
|
||||
continue
|
||||
}
|
||||
txid, err := stun.ParseBindingRequest(pkt)
|
||||
if err != nil {
|
||||
stunNotSTUN.Add(1)
|
||||
continue
|
||||
}
|
||||
if ua.IP.To4() != nil {
|
||||
stunIPv4.Add(1)
|
||||
} else {
|
||||
stunIPv6.Add(1)
|
||||
}
|
||||
addr, _ := netip.AddrFromSlice(ua.IP)
|
||||
res := stun.Response(txid, netip.AddrPortFrom(addr, uint16(ua.Port)))
|
||||
_, err = pc.WriteTo(res, ua)
|
||||
if err != nil {
|
||||
stunWriteError.Add(1)
|
||||
} else {
|
||||
stunSuccess.Add(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var validProdHostname = regexp.MustCompile(`^derp([^.]*)\.tailscale\.com\.?$`)
|
||||
|
||||
func prodAutocertHostPolicy(_ context.Context, host string) error {
|
||||
|
||||
@@ -5,11 +5,13 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"tailscale.com/net/stun"
|
||||
"tailscale.com/tstest/deptest"
|
||||
)
|
||||
|
||||
@@ -37,6 +39,38 @@ func TestProdAutocertHostPolicy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkServerSTUN(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer pc.Close()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go serverSTUNListener(ctx, pc.(*net.UDPConn))
|
||||
addr := pc.LocalAddr().(*net.UDPAddr)
|
||||
|
||||
var resBuf [1500]byte
|
||||
cc, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
tx := stun.NewTxID()
|
||||
req := stun.Request(tx)
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := cc.WriteToUDP(req, addr); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_, _, err := cc.ReadFromUDP(resBuf[:])
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNoContent(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
||||
@@ -158,7 +158,7 @@ func main() {
|
||||
if !ok && (!oiok || !osok) {
|
||||
log.Fatal("set envvar TS_API_KEY to your Tailscale API key or TS_OAUTH_ID and TS_OAUTH_SECRET to your Tailscale OAuth ID and Secret")
|
||||
}
|
||||
if apiKey != "" && (oauthId != "" || oauthSecret != "") {
|
||||
if ok && (oiok || osok) {
|
||||
log.Fatal("set either the envvar TS_API_KEY or TS_OAUTH_ID and TS_OAUTH_SECRET")
|
||||
}
|
||||
var client *http.Client
|
||||
|
||||
@@ -31,12 +31,10 @@ var (
|
||||
//go:embed hello.tmpl.html
|
||||
var embeddedTemplate string
|
||||
|
||||
var localClient tailscale.LocalClient
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *testIP != "" {
|
||||
res, err := localClient.WhoIs(context.Background(), *testIP)
|
||||
res, err := tailscale.WhoIs(context.Background(), *testIP)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -78,7 +76,7 @@ func main() {
|
||||
GetCertificate: func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
switch hi.ServerName {
|
||||
case "hello.ts.net":
|
||||
return localClient.GetCertificate(hi)
|
||||
return tailscale.GetCertificate(hi)
|
||||
case "hello.ipn.dev":
|
||||
c, err := tls.LoadX509KeyPair(
|
||||
"/etc/hello/hello.ipn.dev.crt",
|
||||
@@ -172,7 +170,7 @@ func root(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
who, err := localClient.WhoIs(r.Context(), r.RemoteAddr)
|
||||
who, err := tailscale.WhoIs(r.Context(), r.RemoteAddr)
|
||||
var data tmplData
|
||||
if err != nil {
|
||||
if devMode() {
|
||||
|
||||
@@ -7,6 +7,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
@@ -24,8 +25,10 @@ import (
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"tailscale.com/ipn"
|
||||
tsoperator "tailscale.com/k8s-operator"
|
||||
tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/tstime"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/set"
|
||||
@@ -39,8 +42,9 @@ const (
|
||||
reasonConnectorCleanupInProgress = "ConnectorCleanupInProgress"
|
||||
reasonConnectorInvalid = "ConnectorInvalid"
|
||||
|
||||
messageConnectorCreationFailed = "Failed creating Connector: %v"
|
||||
messageConnectorInvalid = "Connector is invalid: %v"
|
||||
messageConnectorCreationFailed = "Failed creating Connector: %v"
|
||||
messageConnectorInvalid = "Connector is invalid: %v"
|
||||
messageSubnetRouterCleanupFailed = "Failed cleaning up Connector resources: %v"
|
||||
|
||||
shortRequeue = time.Second * 5
|
||||
)
|
||||
@@ -58,20 +62,15 @@ type ConnectorReconciler struct {
|
||||
|
||||
mu sync.Mutex // protects following
|
||||
|
||||
subnetRouters set.Slice[types.UID] // for subnet routers gauge
|
||||
exitNodes set.Slice[types.UID] // for exit nodes gauge
|
||||
connectors set.Slice[types.UID] // for connectors gauge
|
||||
}
|
||||
|
||||
var (
|
||||
// gaugeConnectorResources tracks the overall number of Connectors currently managed by this operator instance.
|
||||
// gaugeConnectorResources tracks the number of Connectors currently managed by this operator instance
|
||||
gaugeConnectorResources = clientmetric.NewGauge("k8s_connector_resources")
|
||||
// gaugeConnectorSubnetRouterResources tracks the number of Connectors managed by this operator instance that are subnet routers.
|
||||
gaugeConnectorSubnetRouterResources = clientmetric.NewGauge("k8s_connector_subnetrouter_resources")
|
||||
// gaugeConnectorExitNodeResources tracks the number of Connectors currently managed by this operator instance that are exit nodes.
|
||||
gaugeConnectorExitNodeResources = clientmetric.NewGauge("k8s_connector_exitnode_resources")
|
||||
)
|
||||
|
||||
func (a *ConnectorReconciler) Reconcile(ctx context.Context, req reconcile.Request) (res reconcile.Result, err error) {
|
||||
func (a *ConnectorReconciler) Reconcile(ctx context.Context, req reconcile.Request) (_ reconcile.Result, err error) {
|
||||
logger := a.logger.With("Connector", req.Name)
|
||||
logger.Debugf("starting reconcile")
|
||||
defer logger.Debugf("reconcile finished")
|
||||
@@ -107,17 +106,21 @@ func (a *ConnectorReconciler) Reconcile(ctx context.Context, req reconcile.Reque
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
reason, message string
|
||||
readyStatus metav1.ConditionStatus
|
||||
)
|
||||
|
||||
oldCnStatus := cn.Status.DeepCopy()
|
||||
setStatus := func(cn *tsapi.Connector, conditionType tsapi.ConnectorConditionType, status metav1.ConditionStatus, reason, message string) (reconcile.Result, error) {
|
||||
tsoperator.SetConnectorCondition(cn, tsapi.ConnectorReady, status, reason, message, cn.Generation, a.clock, logger)
|
||||
defer func() {
|
||||
tsoperator.SetConnectorCondition(cn, tsapi.ConnectorReady, readyStatus, reason, message, cn.Generation, a.clock, logger)
|
||||
if !apiequality.Semantic.DeepEqual(oldCnStatus, cn.Status) {
|
||||
// An error encountered here should get returned by the Reconcile function.
|
||||
if updateErr := a.Client.Status().Update(ctx, cn); updateErr != nil {
|
||||
err = errors.Wrap(err, updateErr.Error())
|
||||
err = updateErr
|
||||
}
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
}()
|
||||
|
||||
if !slices.Contains(cn.Finalizers, FinalizerName) {
|
||||
// This log line is printed exactly once during initial provisioning,
|
||||
@@ -127,33 +130,43 @@ func (a *ConnectorReconciler) Reconcile(ctx context.Context, req reconcile.Reque
|
||||
logger.Infof("ensuring Connector is set up")
|
||||
cn.Finalizers = append(cn.Finalizers, FinalizerName)
|
||||
if err := a.Update(ctx, cn); err != nil {
|
||||
logger.Errorf("error adding finalizer: %w", err)
|
||||
return setStatus(cn, tsapi.ConnectorReady, metav1.ConditionFalse, reasonConnectorCreationFailed, reasonConnectorCreationFailed)
|
||||
err = fmt.Errorf("failed to add finalizer: %w", err)
|
||||
logger.Errorf("error adding finalizer: %v", err)
|
||||
reason = reasonConnectorCreationFailed
|
||||
message = fmt.Sprintf(messageConnectorCreationFailed, err)
|
||||
readyStatus = metav1.ConditionFalse
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := a.validate(cn); err != nil {
|
||||
logger.Errorf("error validating Connector spec: %w", err)
|
||||
message := fmt.Sprintf(messageConnectorInvalid, err)
|
||||
reason = reasonConnectorInvalid
|
||||
message = fmt.Sprintf(messageConnectorInvalid, err)
|
||||
readyStatus = metav1.ConditionFalse
|
||||
a.recorder.Eventf(cn, corev1.EventTypeWarning, reasonConnectorInvalid, message)
|
||||
return setStatus(cn, tsapi.ConnectorReady, metav1.ConditionFalse, reasonConnectorInvalid, message)
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
if err = a.maybeProvisionConnector(ctx, logger, cn); err != nil {
|
||||
logger.Errorf("error creating Connector resources: %w", err)
|
||||
message := fmt.Sprintf(messageConnectorCreationFailed, err)
|
||||
a.recorder.Eventf(cn, corev1.EventTypeWarning, reasonConnectorCreationFailed, message)
|
||||
return setStatus(cn, tsapi.ConnectorReady, metav1.ConditionFalse, reasonConnectorCreationFailed, message)
|
||||
reason = reasonConnectorCreationFailed
|
||||
message = fmt.Sprintf(messageConnectorCreationFailed, err)
|
||||
readyStatus = metav1.ConditionFalse
|
||||
a.recorder.Eventf(cn, corev1.EventTypeWarning, reason, message)
|
||||
} else {
|
||||
logger.Info("Connector resources synced")
|
||||
reason = reasonConnectorCreated
|
||||
message = reasonConnectorCreated
|
||||
readyStatus = metav1.ConditionTrue
|
||||
cn.Status.IsExitNode = cn.Spec.IsExitNode
|
||||
if cn.Spec.SubnetRouter != nil {
|
||||
cn.Status.SubnetRoutes = cn.Spec.SubnetRouter.Routes.Stringify()
|
||||
} else {
|
||||
cn.Status.SubnetRoutes = ""
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info("Connector resources synced")
|
||||
cn.Status.IsExitNode = cn.Spec.ExitNode
|
||||
if cn.Spec.SubnetRouter != nil {
|
||||
cn.Status.SubnetRoutes = cn.Spec.SubnetRouter.AdvertiseRoutes.Stringify()
|
||||
return setStatus(cn, tsapi.ConnectorReady, metav1.ConditionTrue, reasonConnectorCreated, reasonConnectorCreated)
|
||||
}
|
||||
cn.Status.SubnetRoutes = ""
|
||||
return setStatus(cn, tsapi.ConnectorReady, metav1.ConditionTrue, reasonConnectorCreated, reasonConnectorCreated)
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
// maybeProvisionConnector ensures that any new resources required for this
|
||||
@@ -171,37 +184,76 @@ func (a *ConnectorReconciler) maybeProvisionConnector(ctx context.Context, logge
|
||||
ChildResourceLabels: crl,
|
||||
Tags: cn.Spec.Tags.Stringify(),
|
||||
Connector: &connector{
|
||||
isExitNode: cn.Spec.ExitNode,
|
||||
isExitNode: cn.Spec.IsExitNode,
|
||||
},
|
||||
}
|
||||
|
||||
if cn.Spec.SubnetRouter != nil && len(cn.Spec.SubnetRouter.AdvertiseRoutes) > 0 {
|
||||
sts.Connector.routes = cn.Spec.SubnetRouter.AdvertiseRoutes.Stringify()
|
||||
if cn.Spec.SubnetRouter != nil && len(cn.Spec.SubnetRouter.Routes) > 0 {
|
||||
sts.Connector.routes = cn.Spec.SubnetRouter.Routes.Stringify()
|
||||
}
|
||||
|
||||
a.mu.Lock()
|
||||
if sts.Connector.isExitNode {
|
||||
a.exitNodes.Add(cn.UID)
|
||||
} else {
|
||||
a.exitNodes.Remove(cn.UID)
|
||||
}
|
||||
if sts.Connector.routes != "" {
|
||||
a.subnetRouters.Add(cn.GetUID())
|
||||
} else {
|
||||
a.subnetRouters.Remove(cn.GetUID())
|
||||
}
|
||||
a.connectors.Add(cn.UID)
|
||||
gaugeConnectorResources.Set(int64(a.connectors.Len()))
|
||||
a.mu.Unlock()
|
||||
gaugeConnectorSubnetRouterResources.Set(int64(a.subnetRouters.Len()))
|
||||
gaugeConnectorExitNodeResources.Set(int64(a.exitNodes.Len()))
|
||||
var connectors set.Slice[types.UID]
|
||||
connectors.AddSlice(a.exitNodes.Slice())
|
||||
connectors.AddSlice(a.subnetRouters.Slice())
|
||||
gaugeConnectorResources.Set(int64(connectors.Len()))
|
||||
|
||||
_, err := a.ssr.Provision(ctx, logger, sts)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *tailscaleSTSReconciler) tsConfigCM(ctx context.Context, name, namespace string, logger *zap.SugaredLogger, sts *tailscaleSTSConfig) error {
|
||||
confFile, err := confFile(sts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error provisioning config: %v", err)
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(confFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling config file: %v", err)
|
||||
}
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Labels: sts.ChildResourceLabels,
|
||||
},
|
||||
Data: map[string]string{
|
||||
"tailscaled": string(jsonBytes),
|
||||
},
|
||||
}
|
||||
_, err = createOrUpdate(ctx, a.Client, namespace, cm, func(config *corev1.ConfigMap) { config.Labels = cm.Labels; config.Data = cm.Data })
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating a ConfigMap: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func confFile(sts *tailscaleSTSConfig) (*ipn.ConfigVAlpha, error) {
|
||||
var (
|
||||
routes []netip.Prefix
|
||||
err error
|
||||
)
|
||||
if sts.Connector != nil {
|
||||
routes, err = netutil.CalcAdvertiseRoutes(sts.Connector.routes, sts.Connector.isExitNode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error calculating routes: %v", err)
|
||||
}
|
||||
}
|
||||
conf := &ipn.ConfigVAlpha{
|
||||
Version: "alpha0",
|
||||
AdvertiseRoutes: routes,
|
||||
AcceptDNS: "false",
|
||||
Hostname: &sts.Hostname,
|
||||
// Not sure how to log in if it's locked?
|
||||
Locked: "false",
|
||||
}
|
||||
// fix - don't put the key there
|
||||
if sts.key != "" {
|
||||
conf.AuthKey = &sts.key
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func (a *ConnectorReconciler) maybeCleanupConnector(ctx context.Context, logger *zap.SugaredLogger, cn *tsapi.Connector) (bool, error) {
|
||||
if done, err := a.ssr.Cleanup(ctx, logger, childResourceLabels(cn.Name, a.tsnamespace, "connector")); err != nil {
|
||||
return false, fmt.Errorf("failed to cleanup Connector resources: %w", err)
|
||||
@@ -216,15 +268,9 @@ func (a *ConnectorReconciler) maybeCleanupConnector(ctx context.Context, logger
|
||||
// reconciles exit early.
|
||||
logger.Infof("cleaned up Connector resources")
|
||||
a.mu.Lock()
|
||||
a.subnetRouters.Remove(cn.UID)
|
||||
a.exitNodes.Remove(cn.UID)
|
||||
a.mu.Unlock()
|
||||
gaugeConnectorExitNodeResources.Set(int64(a.exitNodes.Len()))
|
||||
gaugeConnectorSubnetRouterResources.Set(int64(a.subnetRouters.Len()))
|
||||
var connectors set.Slice[types.UID]
|
||||
connectors.AddSlice(a.exitNodes.Slice())
|
||||
connectors.AddSlice(a.subnetRouters.Slice())
|
||||
gaugeConnectorResources.Set(int64(connectors.Len()))
|
||||
defer a.mu.Unlock()
|
||||
a.connectors.Remove(cn.UID)
|
||||
gaugeConnectorResources.Set(int64(a.connectors.Len()))
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -232,8 +278,8 @@ func (a *ConnectorReconciler) validate(cn *tsapi.Connector) error {
|
||||
// Connector fields are already validated at apply time with CEL validation
|
||||
// on custom resource fields. The checks here are a backup in case the
|
||||
// CEL validation breaks without us noticing.
|
||||
if !(cn.Spec.SubnetRouter != nil || cn.Spec.ExitNode) {
|
||||
return errors.New("invalid spec: a Connector must expose subnet routes or act as an exit node (or both)")
|
||||
if !(cn.Spec.SubnetRouter != nil || cn.Spec.IsExitNode) {
|
||||
return errors.New("invalid Connector spec- a Connector must be either expose subnet routes or act as exit node (or both)")
|
||||
}
|
||||
if cn.Spec.SubnetRouter == nil {
|
||||
return nil
|
||||
@@ -242,11 +288,11 @@ func (a *ConnectorReconciler) validate(cn *tsapi.Connector) error {
|
||||
}
|
||||
|
||||
func validateSubnetRouter(sb *tsapi.SubnetRouter) error {
|
||||
if len(sb.AdvertiseRoutes) < 1 {
|
||||
if len(sb.Routes) < 1 {
|
||||
return errors.New("invalid subnet router spec: no routes defined")
|
||||
}
|
||||
var err error
|
||||
for _, route := range sb.AdvertiseRoutes {
|
||||
for _, route := range sb.Routes {
|
||||
pfx, e := netip.ParsePrefix(string(route))
|
||||
if e != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("route %s is invalid: %v", route, err))
|
||||
|
||||
@@ -7,6 +7,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/types/ptr"
|
||||
)
|
||||
|
||||
func TestConnector(t *testing.T) {
|
||||
@@ -33,9 +35,9 @@ func TestConnector(t *testing.T) {
|
||||
},
|
||||
Spec: tsapi.ConnectorSpec{
|
||||
SubnetRouter: &tsapi.SubnetRouter{
|
||||
AdvertiseRoutes: []tsapi.Route{"10.40.0.0/14"},
|
||||
Routes: []tsapi.Route{"10.40.0.0/14"},
|
||||
},
|
||||
ExitNode: true,
|
||||
IsExitNode: true,
|
||||
},
|
||||
}
|
||||
fc := fake.NewClientBuilder().
|
||||
@@ -66,57 +68,50 @@ func TestConnector(t *testing.T) {
|
||||
expectReconciled(t, cr, "", "test")
|
||||
fullName, shortName := findGenName(t, fc, "", "test", "connector")
|
||||
|
||||
opts := configOpts{
|
||||
stsName: shortName,
|
||||
secretName: fullName,
|
||||
parentType: "connector",
|
||||
hostname: "test-connector",
|
||||
shouldUseDeclarativeConfig: true,
|
||||
isExitNode: true,
|
||||
subnetRoutes: "10.40.0.0/14",
|
||||
confFileHash: "9321660203effb80983eaecc7b5ac5a8c53934926f46e895b9fe295dcfc5a904",
|
||||
expectEqual(t, fc, expectedSecret(fullName, "", "connector"))
|
||||
opts := connectorSTSOpts{
|
||||
connectorName: "test",
|
||||
stsName: shortName,
|
||||
secretName: fullName,
|
||||
routes: "10.40.0.0/14",
|
||||
isExitNode: true,
|
||||
}
|
||||
expectEqual(t, fc, expectedSecret(t, opts))
|
||||
expectEqual(t, fc, expectedSTS(opts))
|
||||
expectEqual(t, fc, expectedConnectorSTS(opts))
|
||||
|
||||
// Add another route to be advertised.
|
||||
mustUpdate[tsapi.Connector](t, fc, "", "test", func(conn *tsapi.Connector) {
|
||||
conn.Spec.SubnetRouter.AdvertiseRoutes = []tsapi.Route{"10.40.0.0/14", "10.44.0.0/20"}
|
||||
conn.Spec.SubnetRouter.Routes = []tsapi.Route{"10.40.0.0/14", "10.44.0.0/20"}
|
||||
})
|
||||
opts.subnetRoutes = "10.40.0.0/14,10.44.0.0/20"
|
||||
opts.confFileHash = "fb6c4daf67425f983985750cd8d6f2beae77e614fcb34176604571f5623d6862"
|
||||
expectReconciled(t, cr, "", "test")
|
||||
opts.routes = "10.40.0.0/14,10.44.0.0/20"
|
||||
|
||||
expectEqual(t, fc, expectedSTS(opts))
|
||||
expectEqual(t, fc, expectedConnectorSTS(opts))
|
||||
|
||||
// Remove a route.
|
||||
mustUpdate[tsapi.Connector](t, fc, "", "test", func(conn *tsapi.Connector) {
|
||||
conn.Spec.SubnetRouter.AdvertiseRoutes = []tsapi.Route{"10.44.0.0/20"}
|
||||
conn.Spec.SubnetRouter.Routes = []tsapi.Route{"10.44.0.0/20"}
|
||||
})
|
||||
opts.subnetRoutes = "10.44.0.0/20"
|
||||
opts.confFileHash = "bacba177bcfe3849065cf6fee53d658a9bb4144197ac5b861727d69ea99742bb"
|
||||
expectReconciled(t, cr, "", "test")
|
||||
expectEqual(t, fc, expectedSTS(opts))
|
||||
opts.routes = "10.44.0.0/20"
|
||||
expectEqual(t, fc, expectedConnectorSTS(opts))
|
||||
|
||||
// Remove the subnet router.
|
||||
mustUpdate[tsapi.Connector](t, fc, "", "test", func(conn *tsapi.Connector) {
|
||||
conn.Spec.SubnetRouter = nil
|
||||
})
|
||||
opts.subnetRoutes = ""
|
||||
opts.confFileHash = "7c421a99128eb80e79a285a82702f19f8f720615542a15bd794858a6275d8079"
|
||||
expectReconciled(t, cr, "", "test")
|
||||
expectEqual(t, fc, expectedSTS(opts))
|
||||
opts.routes = ""
|
||||
expectEqual(t, fc, expectedConnectorSTS(opts))
|
||||
|
||||
// Re-add the subnet router.
|
||||
mustUpdate[tsapi.Connector](t, fc, "", "test", func(conn *tsapi.Connector) {
|
||||
conn.Spec.SubnetRouter = &tsapi.SubnetRouter{
|
||||
AdvertiseRoutes: []tsapi.Route{"10.44.0.0/20"},
|
||||
Routes: []tsapi.Route{"10.44.0.0/20"},
|
||||
}
|
||||
})
|
||||
opts.subnetRoutes = "10.44.0.0/20"
|
||||
opts.confFileHash = "bacba177bcfe3849065cf6fee53d658a9bb4144197ac5b861727d69ea99742bb"
|
||||
expectReconciled(t, cr, "", "test")
|
||||
expectEqual(t, fc, expectedSTS(opts))
|
||||
opts.routes = "10.44.0.0/20"
|
||||
expectEqual(t, fc, expectedConnectorSTS(opts))
|
||||
|
||||
// Delete the Connector.
|
||||
if err = fc.Delete(context.Background(), cn); err != nil {
|
||||
@@ -141,36 +136,23 @@ func TestConnector(t *testing.T) {
|
||||
},
|
||||
Spec: tsapi.ConnectorSpec{
|
||||
SubnetRouter: &tsapi.SubnetRouter{
|
||||
AdvertiseRoutes: []tsapi.Route{"10.40.0.0/14"},
|
||||
Routes: []tsapi.Route{"10.40.0.0/14"},
|
||||
},
|
||||
},
|
||||
}
|
||||
opts.subnetRoutes = "10.44.0.0/14"
|
||||
opts.isExitNode = false
|
||||
mustCreate(t, fc, cn)
|
||||
expectReconciled(t, cr, "", "test")
|
||||
fullName, shortName = findGenName(t, fc, "", "test", "connector")
|
||||
|
||||
opts = configOpts{
|
||||
stsName: shortName,
|
||||
secretName: fullName,
|
||||
parentType: "connector",
|
||||
shouldUseDeclarativeConfig: true,
|
||||
subnetRoutes: "10.40.0.0/14",
|
||||
hostname: "test-connector",
|
||||
confFileHash: "57d922331890c9b1c8c6ae664394cb254334c551d9cd9db14537b5d9da9fb17e",
|
||||
expectEqual(t, fc, expectedSecret(fullName, "", "connector"))
|
||||
opts = connectorSTSOpts{
|
||||
connectorName: "test",
|
||||
stsName: shortName,
|
||||
secretName: fullName,
|
||||
routes: "10.40.0.0/14",
|
||||
isExitNode: false,
|
||||
}
|
||||
expectEqual(t, fc, expectedSecret(t, opts))
|
||||
expectEqual(t, fc, expectedSTS(opts))
|
||||
|
||||
// Add an exit node.
|
||||
mustUpdate[tsapi.Connector](t, fc, "", "test", func(conn *tsapi.Connector) {
|
||||
conn.Spec.ExitNode = true
|
||||
})
|
||||
opts.isExitNode = true
|
||||
opts.confFileHash = "1499b591fd97a50f0330db6ec09979792c49890cf31f5da5bb6a3f50dba1e77a"
|
||||
expectReconciled(t, cr, "", "test")
|
||||
expectEqual(t, fc, expectedSTS(opts))
|
||||
expectEqual(t, fc, expectedConnectorSTS(opts))
|
||||
|
||||
// Delete the Connector.
|
||||
if err = fc.Delete(context.Background(), cn); err != nil {
|
||||
@@ -183,3 +165,89 @@ func TestConnector(t *testing.T) {
|
||||
expectMissing[appsv1.StatefulSet](t, fc, "operator-ns", shortName)
|
||||
expectMissing[corev1.Secret](t, fc, "operator-ns", fullName)
|
||||
}
|
||||
|
||||
type connectorSTSOpts struct {
|
||||
stsName string
|
||||
secretName string
|
||||
connectorName string
|
||||
hostname string
|
||||
routes string
|
||||
isExitNode bool
|
||||
}
|
||||
|
||||
func expectedConnectorSTS(opts connectorSTSOpts) *appsv1.StatefulSet {
|
||||
var hostname string
|
||||
if opts.hostname != "" {
|
||||
hostname = opts.hostname
|
||||
} else {
|
||||
hostname = opts.connectorName + "-connector"
|
||||
}
|
||||
containerEnv := []corev1.EnvVar{
|
||||
{Name: "TS_USERSPACE", Value: "false"},
|
||||
{Name: "TS_AUTH_ONCE", Value: "true"},
|
||||
{Name: "TS_KUBE_SECRET", Value: opts.secretName},
|
||||
{Name: "TS_HOSTNAME", Value: hostname},
|
||||
{Name: "TS_EXTRA_ARGS", Value: fmt.Sprintf("--advertise-exit-node=%v", opts.isExitNode)},
|
||||
{Name: "TS_ROUTES", Value: opts.routes},
|
||||
}
|
||||
sts := &appsv1.StatefulSet{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "StatefulSet",
|
||||
APIVersion: "apps/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: opts.stsName,
|
||||
Namespace: "operator-ns",
|
||||
Labels: map[string]string{
|
||||
"tailscale.com/managed": "true",
|
||||
"tailscale.com/parent-resource": "test",
|
||||
"tailscale.com/parent-resource-ns": "",
|
||||
"tailscale.com/parent-resource-type": "connector",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
Replicas: ptr.To[int32](1),
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"app": "1234-UID"},
|
||||
},
|
||||
ServiceName: opts.stsName,
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
DeletionGracePeriodSeconds: ptr.To[int64](10),
|
||||
Labels: map[string]string{"app": "1234-UID"},
|
||||
Annotations: map[string]string{
|
||||
"tailscale.com/operator-last-set-hostname": hostname,
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
ServiceAccountName: "proxies",
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "sysctler",
|
||||
Image: "tailscale/tailscale",
|
||||
Command: []string{"/bin/sh"},
|
||||
Args: []string{"-c", "sysctl -w net.ipv4.ip_forward=1 net.ipv6.conf.all.forwarding=1"},
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "tailscale",
|
||||
Image: "tailscale/tailscale",
|
||||
Env: containerEnv,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"NET_ADMIN"},
|
||||
},
|
||||
},
|
||||
ImagePullPolicy: "Always",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return sts
|
||||
}
|
||||
|
||||
@@ -49,8 +49,6 @@ spec:
|
||||
image: {{ .Values.operatorConfig.image.repo }}{{- if .Values.operatorConfig.image.digest -}}{{ printf "@%s" .Values.operatorConfig.image.digest}}{{- else -}}{{ printf "%s" $operatorTag }}{{- end }}
|
||||
imagePullPolicy: {{ .Values.operatorConfig.image.pullPolicy }}
|
||||
env:
|
||||
- name: OPERATOR_INITIAL_TAGS
|
||||
value: {{ join "," .Values.operatorConfig.defaultTags }}
|
||||
- name: OPERATOR_HOSTNAME
|
||||
value: {{ .Values.operatorConfig.hostname }}
|
||||
- name: OPERATOR_SECRET
|
||||
@@ -61,6 +59,8 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: ENABLE_CONNECTOR
|
||||
value: "{{ .Values.enableConnector }}"
|
||||
- name: CLIENT_ID_FILE
|
||||
value: /oauth/client_id
|
||||
- name: CLIENT_SECRET_FILE
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: IngressClass
|
||||
metadata:
|
||||
name: tailscale # class name currently can not be changed
|
||||
annotations: {} # we do not support default IngressClass annotation https://kubernetes.io/docs/concepts/services-networking/ingress/#default-ingress-class
|
||||
spec:
|
||||
controller: tailscale.com/ts-ingress # controller name currently can not be changed
|
||||
# parameters: {} # currently no parameters are supported
|
||||
@@ -18,12 +18,12 @@ rules:
|
||||
- apiGroups: ["networking.k8s.io"]
|
||||
resources: ["ingresses", "ingresses/status"]
|
||||
verbs: ["*"]
|
||||
- apiGroups: ["networking.k8s.io"]
|
||||
resources: ["ingressclasses"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["tailscale.com"]
|
||||
resources: ["connectors", "connectors/status"]
|
||||
verbs: ["get", "list", "watch", "update"]
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
verbs: ["*"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
|
||||
@@ -8,20 +8,13 @@ oauth: {}
|
||||
# clientId: ""
|
||||
# clientSecret: ""
|
||||
|
||||
# installCRDs determines whether tailscale.com CRDs should be installed as part
|
||||
# of chart installation. We do not use Helm's CRD installation mechanism as that
|
||||
# does not allow for upgrading CRDs.
|
||||
# https://helm.sh/docs/chart_best_practices/custom_resource_definitions/
|
||||
installCRDs: "true"
|
||||
# enableConnector determines whether the operator should reconcile
|
||||
# connector.tailscale.com custom resources. If set to true you have to install
|
||||
# connector CRD in a separate step.
|
||||
# You can do so by running 'kubectl apply -f ./cmd/k8s-operator/deploy/crds'.
|
||||
enableConnector: "false"
|
||||
|
||||
operatorConfig:
|
||||
# ACL tag that operator will be tagged with. Operator must be made owner of
|
||||
# these tags
|
||||
# https://tailscale.com/kb/1236/kubernetes-operator/?q=operator#setting-up-the-kubernetes-operator
|
||||
# Multiple tags are defined as array items and passed to the operator as a comma-separated string
|
||||
defaultTags:
|
||||
- "tag:k8s-operator"
|
||||
|
||||
image:
|
||||
repo: tailscale/k8s-operator
|
||||
# Digest will be prioritized over tag. If neither are set appVersion will be
|
||||
|
||||
@@ -47,34 +47,33 @@ spec:
|
||||
description: ConnectorSpec describes the desired Tailscale component.
|
||||
type: object
|
||||
properties:
|
||||
exitNode:
|
||||
description: ExitNode defines whether the Connector node should act as a Tailscale exit node. Defaults to false. https://tailscale.com/kb/1103/exit-nodes
|
||||
type: boolean
|
||||
hostname:
|
||||
description: Hostname is the tailnet hostname that should be assigned to the Connector node. If unset, hostname defaults to <connector name>-connector. Hostname can contain lower case letters, numbers and dashes, it must not start or end with a dash and must be between 2 and 63 characters long.
|
||||
description: Hostname is the tailnet hostname that should be assigned to the Connector node. If unset, hostname is defaulted to <connector name>-connector. Hostname can contain lower case letters, numbers and dashes, it must not start or end with a dash and must be between 2 and 63 characters long.
|
||||
type: string
|
||||
pattern: ^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$
|
||||
isExitNode:
|
||||
description: IsExitNode defines whether the Connector node should act as a Tailscale exit node. Defaults to false. https://tailscale.com/kb/1103/exit-nodes
|
||||
type: boolean
|
||||
subnetRouter:
|
||||
description: SubnetRouter defines subnet routes that the Connector node should expose to tailnet. If unset, none are exposed. https://tailscale.com/kb/1019/subnets/
|
||||
type: object
|
||||
required:
|
||||
- advertiseRoutes
|
||||
- routes
|
||||
properties:
|
||||
advertiseRoutes:
|
||||
description: AdvertiseRoutes refer to CIDRs that the subnet router should make available. Route values must be strings that represent a valid IPv4 or IPv6 CIDR range. Values can be Tailscale 4via6 subnet routes. https://tailscale.com/kb/1201/4via6-subnets/
|
||||
routes:
|
||||
description: Routes refer to in-cluster CIDRs that the subnet router should make available. Route values must be strings that represent a valid IPv4 or IPv6 CIDR range. Values can be Tailscale 4via6 subnet routes. https://tailscale.com/kb/1201/4via6-subnets/
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: string
|
||||
format: cidr
|
||||
tags:
|
||||
description: Tags that the Tailscale node will be tagged with. Defaults to [tag:k8s]. To autoapprove the subnet routes or exit node defined by a Connector, you can configure Tailscale ACLs to give these tags the necessary permissions. See https://tailscale.com/kb/1018/acls/#auto-approvers-for-routes-and-exit-nodes. If you specify custom tags here, you must also make the operator an owner of these tags. See https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator. Tags cannot be changed once a Connector node has been created. Tag values must be in form ^tag:[a-zA-Z][a-zA-Z0-9-]*$.
|
||||
description: Tags that the Tailscale node will be tagged with. Defaults to [tag:k8s]. To autoapprove the subnet routes or exit node defined by a Connector, you can configure Tailscale ACLs to give these tags the necessary permissions. See https://tailscale.com/kb/1018/acls/#auto-approvers-for-routes-and-exit-nodes If you specify custom tags here, you must also make the operator an owner of these tags. See https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator. Tags cannot be changed once a Connector node has been created. Tag values must be in form ^tag:[a-zA-Z][a-zA-Z0-9-]*$.
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$
|
||||
x-kubernetes-validations:
|
||||
- rule: has(self.subnetRouter) || self.exitNode == true
|
||||
- rule: has(self.subnetRouter) || self.isExitNode == true
|
||||
message: A Connector needs to be either an exit node or a subnet router, or both.
|
||||
status:
|
||||
description: ConnectorStatus describes the status of the Connector. This is set and managed by the Tailscale operator.
|
||||
|
||||
@@ -13,7 +13,7 @@ spec:
|
||||
- "tag:prod"
|
||||
hostname: ts-prod
|
||||
subnetRouter:
|
||||
advertiseRoutes:
|
||||
routes:
|
||||
- "10.40.0.0/14"
|
||||
- "192.168.0.0/14"
|
||||
exitNode: true
|
||||
isExitNode: true
|
||||
|
||||
@@ -27,132 +27,6 @@ metadata:
|
||||
name: proxies
|
||||
namespace: tailscale
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.13.0
|
||||
name: connectors.tailscale.com
|
||||
spec:
|
||||
group: tailscale.com
|
||||
names:
|
||||
kind: Connector
|
||||
listKind: ConnectorList
|
||||
plural: connectors
|
||||
shortNames:
|
||||
- cn
|
||||
singular: connector
|
||||
scope: Cluster
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- description: CIDR ranges exposed to tailnet by a subnet router defined via this Connector instance.
|
||||
jsonPath: .status.subnetRoutes
|
||||
name: SubnetRoutes
|
||||
type: string
|
||||
- description: Whether this Connector instance defines an exit node.
|
||||
jsonPath: .status.isExitNode
|
||||
name: IsExitNode
|
||||
type: string
|
||||
- description: Status of the deployed Connector resources.
|
||||
jsonPath: .status.conditions[?(@.type == "ConnectorReady")].reason
|
||||
name: Status
|
||||
type: string
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: ConnectorSpec describes the desired Tailscale component.
|
||||
properties:
|
||||
exitNode:
|
||||
description: ExitNode defines whether the Connector node should act as a Tailscale exit node. Defaults to false. https://tailscale.com/kb/1103/exit-nodes
|
||||
type: boolean
|
||||
hostname:
|
||||
description: Hostname is the tailnet hostname that should be assigned to the Connector node. If unset, hostname defaults to <connector name>-connector. Hostname can contain lower case letters, numbers and dashes, it must not start or end with a dash and must be between 2 and 63 characters long.
|
||||
pattern: ^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$
|
||||
type: string
|
||||
subnetRouter:
|
||||
description: SubnetRouter defines subnet routes that the Connector node should expose to tailnet. If unset, none are exposed. https://tailscale.com/kb/1019/subnets/
|
||||
properties:
|
||||
advertiseRoutes:
|
||||
description: AdvertiseRoutes refer to CIDRs that the subnet router should make available. Route values must be strings that represent a valid IPv4 or IPv6 CIDR range. Values can be Tailscale 4via6 subnet routes. https://tailscale.com/kb/1201/4via6-subnets/
|
||||
items:
|
||||
format: cidr
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
required:
|
||||
- advertiseRoutes
|
||||
type: object
|
||||
tags:
|
||||
description: Tags that the Tailscale node will be tagged with. Defaults to [tag:k8s]. To autoapprove the subnet routes or exit node defined by a Connector, you can configure Tailscale ACLs to give these tags the necessary permissions. See https://tailscale.com/kb/1018/acls/#auto-approvers-for-routes-and-exit-nodes. If you specify custom tags here, you must also make the operator an owner of these tags. See https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator. Tags cannot be changed once a Connector node has been created. Tag values must be in form ^tag:[a-zA-Z][a-zA-Z0-9-]*$.
|
||||
items:
|
||||
pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: A Connector needs to be either an exit node or a subnet router, or both.
|
||||
rule: has(self.subnetRouter) || self.exitNode == true
|
||||
status:
|
||||
description: ConnectorStatus describes the status of the Connector. This is set and managed by the Tailscale operator.
|
||||
properties:
|
||||
conditions:
|
||||
description: List of status conditions to indicate the status of the Connector. Known condition types are `ConnectorReady`.
|
||||
items:
|
||||
description: ConnectorCondition contains condition information for a Connector.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime is the timestamp corresponding to the last status change of this condition.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: Message is a human readable description of the details of the last transition, complementing reason.
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Connector.
|
||||
format: int64
|
||||
type: integer
|
||||
reason:
|
||||
description: Reason is a brief machine readable explanation for the condition's last transition.
|
||||
type: string
|
||||
status:
|
||||
description: Status of the condition, one of ('True', 'False', 'Unknown').
|
||||
type: string
|
||||
type:
|
||||
description: Type of the condition, known values are (`SubnetRouterReady`).
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
isExitNode:
|
||||
description: IsExitNode is set to true if the Connector acts as an exit node.
|
||||
type: boolean
|
||||
subnetRoutes:
|
||||
description: SubnetRoutes are the routes currently exposed to tailnet via this Connector instance.
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
@@ -173,14 +47,6 @@ rules:
|
||||
- ingresses/status
|
||||
verbs:
|
||||
- '*'
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingressclasses
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- tailscale.com
|
||||
resources:
|
||||
@@ -284,8 +150,6 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: OPERATOR_INITIAL_TAGS
|
||||
value: tag:k8s-operator
|
||||
- name: OPERATOR_HOSTNAME
|
||||
value: tailscale-operator
|
||||
- name: OPERATOR_SECRET
|
||||
@@ -296,6 +160,8 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: ENABLE_CONNECTOR
|
||||
value: "false"
|
||||
- name: CLIENT_ID_FILE
|
||||
value: /oauth/client_id
|
||||
- name: CLIENT_SECRET_FILE
|
||||
@@ -322,11 +188,3 @@ spec:
|
||||
- name: oauth
|
||||
secret:
|
||||
secretName: operator-oauth
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: IngressClass
|
||||
metadata:
|
||||
annotations: {}
|
||||
name: tailscale
|
||||
spec:
|
||||
controller: tailscale.com/ts-ingress
|
||||
|
||||
@@ -18,49 +18,15 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
operatorDeploymentFilesPath = "cmd/k8s-operator/deploy"
|
||||
crdPath = operatorDeploymentFilesPath + "/crds/tailscale.com_connectors.yaml"
|
||||
helmTemplatesPath = operatorDeploymentFilesPath + "/chart/templates"
|
||||
crdTemplatePath = helmTemplatesPath + "/connectors.yaml"
|
||||
|
||||
helmConditionalStart = "{{ if .Values.installCRDs -}}\n"
|
||||
helmConditionalEnd = "{{- end -}}"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
log.Fatalf("usage ./generate [staticmanifests|helmcrd]")
|
||||
}
|
||||
repoRoot := "../../"
|
||||
switch os.Args[1] {
|
||||
case "helmcrd": // insert CRD to Helm templates behind a installCRDs=true conditional check
|
||||
log.Print("Adding Connector CRD to Helm templates")
|
||||
if err := generate("./"); err != nil {
|
||||
log.Fatalf("error adding Connector CRD to Helm templates: %v", err)
|
||||
}
|
||||
return
|
||||
case "staticmanifests": // generate static manifests from Helm templates (including the CRD)
|
||||
default:
|
||||
log.Fatalf("unknown option %s, known options are 'staticmanifests', 'helmcrd'", os.Args[1])
|
||||
}
|
||||
log.Printf("Inserting CRD into the Helm templates")
|
||||
if err := generate(repoRoot); err != nil {
|
||||
log.Fatalf("error adding Connector CRD to Helm templates: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := cleanup(repoRoot); err != nil {
|
||||
log.Fatalf("error cleaning up generated resources")
|
||||
}
|
||||
}()
|
||||
log.Print("Templating Helm chart contents")
|
||||
helmTmplCmd := exec.Command("./tool/helm", "template", "operator", "./cmd/k8s-operator/deploy/chart",
|
||||
cmd := exec.Command("./tool/helm", "template", "operator", "./cmd/k8s-operator/deploy/chart",
|
||||
"--namespace=tailscale")
|
||||
helmTmplCmd.Dir = repoRoot
|
||||
cmd.Dir = repoRoot
|
||||
var out bytes.Buffer
|
||||
helmTmplCmd.Stdout = &out
|
||||
helmTmplCmd.Stderr = os.Stderr
|
||||
if err := helmTmplCmd.Run(); err != nil {
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Fatalf("error templating helm manifests: %v", err)
|
||||
}
|
||||
|
||||
@@ -88,6 +54,7 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatalf("failed read from input data: %v", err)
|
||||
}
|
||||
|
||||
bytes, err := yaml.Marshal(document)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to marshal YAML document: %v", err)
|
||||
@@ -105,35 +72,3 @@ func main() {
|
||||
log.Fatalf("error writing new file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func generate(baseDir string) error {
|
||||
log.Print("Placing Connector CRD into Helm templates..")
|
||||
chartBytes, err := os.ReadFile(filepath.Join(baseDir, crdPath))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading CRD contents: %w", err)
|
||||
}
|
||||
// Place a new temporary Helm template file with the templated CRD
|
||||
// contents into Helm templates.
|
||||
file, err := os.Create(filepath.Join(baseDir, crdTemplatePath))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating CRD template file: %w", err)
|
||||
}
|
||||
if _, err := file.Write([]byte(helmConditionalStart)); err != nil {
|
||||
return fmt.Errorf("error writing helm if statement start: %w", err)
|
||||
}
|
||||
if _, err := file.Write(chartBytes); err != nil {
|
||||
return fmt.Errorf("error writing chart bytes: %w", err)
|
||||
}
|
||||
if _, err := file.Write([]byte(helmConditionalEnd)); err != nil {
|
||||
return fmt.Errorf("error writing helm if-statement end: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanup(baseDir string) error {
|
||||
log.Print("Cleaning up CRD from Helm templates")
|
||||
if err := os.Remove(filepath.Join(baseDir, crdTemplatePath)); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("error cleaning up CRD template: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !plan9 && !windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_generate(t *testing.T) {
|
||||
base, err := os.Getwd()
|
||||
base = filepath.Join(base, "../../../")
|
||||
if err != nil {
|
||||
t.Fatalf("error getting current working directory: %v", err)
|
||||
}
|
||||
defer cleanup(base)
|
||||
if err := generate(base); err != nil {
|
||||
t.Fatalf("CRD template generation: %v", err)
|
||||
}
|
||||
|
||||
tempDir := t.TempDir()
|
||||
helmCLIPath := filepath.Join(base, "tool/helm")
|
||||
helmChartTemplatesPath := filepath.Join(base, "cmd/k8s-operator/deploy/chart")
|
||||
helmPackageCmd := exec.Command(helmCLIPath, "package", helmChartTemplatesPath, "--destination", tempDir, "--version", "0.0.1")
|
||||
helmPackageCmd.Stderr = os.Stderr
|
||||
helmPackageCmd.Stdout = os.Stdout
|
||||
if err := helmPackageCmd.Run(); err != nil {
|
||||
t.Fatalf("error packaging Helm chart: %v", err)
|
||||
}
|
||||
helmPackagePath := filepath.Join(tempDir, "tailscale-operator-0.0.1.tgz")
|
||||
helmLintCmd := exec.Command(helmCLIPath, "lint", helmPackagePath)
|
||||
helmLintCmd.Stderr = os.Stderr
|
||||
helmLintCmd.Stdout = os.Stdout
|
||||
if err := helmLintCmd.Run(); err != nil {
|
||||
t.Fatalf("Helm chart linter failed: %v", err)
|
||||
}
|
||||
|
||||
// Test that default Helm install contains the CRD
|
||||
installContentsWithCRD := bytes.NewBuffer([]byte{})
|
||||
helmTemplateWithCRDCmd := exec.Command(helmCLIPath, "template", helmPackagePath)
|
||||
helmTemplateWithCRDCmd.Stderr = os.Stderr
|
||||
helmTemplateWithCRDCmd.Stdout = installContentsWithCRD
|
||||
if err := helmTemplateWithCRDCmd.Run(); err != nil {
|
||||
t.Fatalf("templating Helm chart with CRDs failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(installContentsWithCRD.String(), "name: connectors.tailscale.com") {
|
||||
t.Errorf("CRD not found in default chart install")
|
||||
}
|
||||
|
||||
// Test that CRD can be excluded from Helm chart install
|
||||
installContentsWithoutCRD := bytes.NewBuffer([]byte{})
|
||||
helmTemplateWithoutCRDCmd := exec.Command(helmCLIPath, "template", helmPackagePath, "--set", "installCRDs=false")
|
||||
helmTemplateWithoutCRDCmd.Stderr = os.Stderr
|
||||
helmTemplateWithoutCRDCmd.Stdout = installContentsWithoutCRD
|
||||
if err := helmTemplateWithoutCRDCmd.Run(); err != nil {
|
||||
t.Fatalf("templating Helm chart without CRDs failed: %v", err)
|
||||
}
|
||||
if strings.Contains(installContentsWithoutCRD.String(), "name: connectors.tailscale.com") {
|
||||
t.Errorf("CRD found in chart install that should not contain a CRD")
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,10 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -28,12 +26,6 @@ import (
|
||||
"tailscale.com/util/set"
|
||||
)
|
||||
|
||||
const (
|
||||
tailscaleIngressClassName = "tailscale" // ingressClass.metadata.name for tailscale IngressClass resource
|
||||
tailscaleIngressControllerName = "tailscale.com/ts-ingress" // ingressClass.spec.controllerName for tailscale IngressClass resource
|
||||
ingressClassDefaultAnnotation = "ingressclass.kubernetes.io/is-default-class" // we do not support this https://kubernetes.io/docs/concepts/services-networking/ingress/#default-ingress-class
|
||||
)
|
||||
|
||||
type IngressReconciler struct {
|
||||
client.Client
|
||||
|
||||
@@ -117,10 +109,6 @@ func (a *IngressReconciler) maybeCleanup(ctx context.Context, logger *zap.Sugare
|
||||
// This function adds a finalizer to ing, ensuring that we can handle orderly
|
||||
// deprovisioning later.
|
||||
func (a *IngressReconciler) maybeProvision(ctx context.Context, logger *zap.SugaredLogger, ing *networkingv1.Ingress) error {
|
||||
if err := a.validateIngressClass(ctx); err != nil {
|
||||
logger.Warnf("error validating tailscale IngressClass: %v. In future this might be a terminal error.", err)
|
||||
|
||||
}
|
||||
if !slices.Contains(ing.Finalizers, FinalizerName) {
|
||||
// This log line is printed exactly once during initial provisioning,
|
||||
// because once the finalizer is in place this block gets skipped. So,
|
||||
@@ -217,25 +205,10 @@ func (a *IngressReconciler) maybeProvision(ctx context.Context, logger *zap.Suga
|
||||
continue
|
||||
}
|
||||
for _, p := range rule.HTTP.Paths {
|
||||
// Send a warning if folks use Exact path type - to make
|
||||
// it easier for us to support Exact path type matching
|
||||
// in the future if needed.
|
||||
// https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types
|
||||
if *p.PathType == networkingv1.PathTypeExact {
|
||||
msg := "Exact path type strict matching is currently not supported and requests will be routed as for Prefix path type. This behaviour might change in the future."
|
||||
logger.Warnf(fmt.Sprintf("Unsupported Path type exact for path %s. %s", p.Path, msg))
|
||||
a.recorder.Eventf(ing, corev1.EventTypeWarning, "UnsupportedPathTypeExact", msg)
|
||||
}
|
||||
addIngressBackend(&p.Backend, p.Path)
|
||||
}
|
||||
}
|
||||
|
||||
if len(web.Handlers) == 0 {
|
||||
logger.Warn("Ingress contains no valid backends")
|
||||
a.recorder.Eventf(ing, corev1.EventTypeWarning, "NoValidBackends", "no valid backends")
|
||||
return nil
|
||||
}
|
||||
|
||||
crl := childResourceLabels(ing.Name, ing.Namespace, "ingress")
|
||||
var tags []string
|
||||
if tstr, ok := ing.Annotations[AnnotationTags]; ok {
|
||||
@@ -294,28 +267,5 @@ func (a *IngressReconciler) maybeProvision(ctx context.Context, logger *zap.Suga
|
||||
func (a *IngressReconciler) shouldExpose(ing *networkingv1.Ingress) bool {
|
||||
return ing != nil &&
|
||||
ing.Spec.IngressClassName != nil &&
|
||||
*ing.Spec.IngressClassName == tailscaleIngressClassName
|
||||
}
|
||||
|
||||
// validateIngressClass attempts to validate that 'tailscale' IngressClass
|
||||
// included in Tailscale installation manifests exists and has not been modified
|
||||
// to attempt to enable features that we do not support.
|
||||
func (a *IngressReconciler) validateIngressClass(ctx context.Context) error {
|
||||
ic := &networkingv1.IngressClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: tailscaleIngressClassName,
|
||||
},
|
||||
}
|
||||
if err := a.Get(ctx, client.ObjectKeyFromObject(ic), ic); apierrors.IsNotFound(err) {
|
||||
return errors.New("Tailscale IngressClass not found in cluster. Latest installation manifests include a tailscale IngressClass - please update")
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("error retrieving 'tailscale' IngressClass: %w", err)
|
||||
}
|
||||
if ic.Spec.Controller != tailscaleIngressControllerName {
|
||||
return fmt.Errorf("Tailscale Ingress class controller name %s does not match tailscale Ingress controller name %s. Ensure that you are using 'tailscale' IngressClass from latest Tailscale installation manifests", ic.Spec.Controller, tailscaleIngressControllerName)
|
||||
}
|
||||
if ic.GetAnnotations()[ingressClassDefaultAnnotation] != "" {
|
||||
return fmt.Errorf("%s annotation is set on 'tailscale' IngressClass, but Tailscale Ingress controller does not support default Ingress class. Ensure that you are using 'tailscale' IngressClass from latest Tailscale installation manifests", ingressClassDefaultAnnotation)
|
||||
}
|
||||
return nil
|
||||
*ing.Spec.IngressClassName == "tailscale"
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -17,7 +16,6 @@ import (
|
||||
"github.com/go-logr/zapr"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
@@ -41,11 +39,12 @@ import (
|
||||
"tailscale.com/tsnet"
|
||||
"tailscale.com/tstime"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/must"
|
||||
"tailscale.com/version"
|
||||
)
|
||||
|
||||
// Generate static manifests for deploying Tailscale operator on Kubernetes from the operator's Helm chart.
|
||||
//go:generate go run tailscale.com/cmd/k8s-operator/generate staticmanifests
|
||||
//go:generate go run tailscale.com/cmd/k8s-operator/generate
|
||||
|
||||
// Generate Connector CustomResourceDefinition yaml from its Go types.
|
||||
//go:generate go run sigs.k8s.io/controller-tools/cmd/controller-gen crd schemapatch:manifests=./deploy/crds output:dir=./deploy/crds paths=../../k8s-operator/apis/...
|
||||
@@ -56,12 +55,13 @@ func main() {
|
||||
tailscale.I_Acknowledge_This_API_Is_Unstable = true
|
||||
|
||||
var (
|
||||
tsNamespace = defaultEnv("OPERATOR_NAMESPACE", "")
|
||||
tslogging = defaultEnv("OPERATOR_LOGGING", "info")
|
||||
image = defaultEnv("PROXY_IMAGE", "tailscale/tailscale:latest")
|
||||
priorityClassName = defaultEnv("PROXY_PRIORITY_CLASS_NAME", "")
|
||||
tags = defaultEnv("PROXY_TAGS", "tag:k8s")
|
||||
tsFirewallMode = defaultEnv("PROXY_FIREWALL_MODE", "")
|
||||
// tsNamespace = defaultEnv("OPERATOR_NAMESPACE", "")
|
||||
tslogging = defaultEnv("OPERATOR_LOGGING", "info")
|
||||
// image = defaultEnv("PROXY_IMAGE", "tailscale/tailscale:latest")
|
||||
// priorityClassName = defaultEnv("PROXY_PRIORITY_CLASS_NAME", "")
|
||||
// tags = defaultEnv("PROXY_TAGS", "tag:k8s")
|
||||
// tsFirewallMode = defaultEnv("PROXY_FIREWALL_MODE", "")
|
||||
// tsEnableConnector = defaultBool("ENABLE_CONNECTOR", false)
|
||||
)
|
||||
|
||||
var opts []kzap.Opts
|
||||
@@ -79,20 +79,22 @@ func main() {
|
||||
// The operator can run either as a plain operator or it can
|
||||
// additionally act as api-server proxy
|
||||
// https://tailscale.com/kb/1236/kubernetes-operator/?q=kubernetes#accessing-the-kubernetes-control-plane-using-an-api-server-proxy.
|
||||
mode := parseAPIProxyMode()
|
||||
// mode := parseAPIProxyMode()
|
||||
mode := apiserverProxyModeNoAuth
|
||||
if mode == apiserverProxyModeDisabled {
|
||||
hostinfo.SetApp("k8s-operator")
|
||||
} else {
|
||||
hostinfo.SetApp("k8s-operator-proxy")
|
||||
}
|
||||
|
||||
s, tsClient := initTSNet(zlog)
|
||||
s, _ := initTSNet(zlog)
|
||||
defer s.Close()
|
||||
restConfig := config.GetConfigOrDie()
|
||||
restConfig := must.Get(config.GetConfigWithContext(""))
|
||||
maybeLaunchAPIServerProxy(zlog, restConfig, s, mode)
|
||||
select {}
|
||||
// TODO (irbekrm): gather the reconciler options into an opts struct
|
||||
// rather than passing a million of them in one by one.
|
||||
runReconcilers(zlog, s, tsNamespace, restConfig, tsClient, image, priorityClassName, tags, tsFirewallMode)
|
||||
// runReconcilers(zlog, s, tsNamespace, restConfig, tsClient, image, priorityClassName, tags, tsFirewallMode, tsEnableConnector)
|
||||
}
|
||||
|
||||
// initTSNet initializes the tsnet.Server and logs in to Tailscale. It uses the
|
||||
@@ -100,31 +102,31 @@ func main() {
|
||||
// with Tailscale.
|
||||
func initTSNet(zlog *zap.SugaredLogger) (*tsnet.Server, *tailscale.Client) {
|
||||
var (
|
||||
clientIDPath = defaultEnv("CLIENT_ID_FILE", "")
|
||||
clientSecretPath = defaultEnv("CLIENT_SECRET_FILE", "")
|
||||
hostname = defaultEnv("OPERATOR_HOSTNAME", "tailscale-operator")
|
||||
kubeSecret = defaultEnv("OPERATOR_SECRET", "")
|
||||
operatorTags = defaultEnv("OPERATOR_INITIAL_TAGS", "tag:k8s-operator")
|
||||
// clientIDPath = defaultEnv("CLIENT_ID_FILE", "")
|
||||
// clientSecretPath = defaultEnv("CLIENT_SECRET_FILE", "")
|
||||
hostname = defaultEnv("OPERATOR_HOSTNAME", "tailscale-operator")
|
||||
kubeSecret = defaultEnv("OPERATOR_SECRET", "")
|
||||
// operatorTags = defaultEnv("OPERATOR_INITIAL_TAGS", "tag:k8s-operator")
|
||||
)
|
||||
startlog := zlog.Named("startup")
|
||||
if clientIDPath == "" || clientSecretPath == "" {
|
||||
startlog.Fatalf("CLIENT_ID_FILE and CLIENT_SECRET_FILE must be set")
|
||||
}
|
||||
clientID, err := os.ReadFile(clientIDPath)
|
||||
if err != nil {
|
||||
startlog.Fatalf("reading client ID %q: %v", clientIDPath, err)
|
||||
}
|
||||
clientSecret, err := os.ReadFile(clientSecretPath)
|
||||
if err != nil {
|
||||
startlog.Fatalf("reading client secret %q: %v", clientSecretPath, err)
|
||||
}
|
||||
credentials := clientcredentials.Config{
|
||||
ClientID: string(clientID),
|
||||
ClientSecret: string(clientSecret),
|
||||
TokenURL: "https://login.tailscale.com/api/v2/oauth/token",
|
||||
}
|
||||
tsClient := tailscale.NewClient("-", nil)
|
||||
tsClient.HTTPClient = credentials.Client(context.Background())
|
||||
// if clientIDPath == "" || clientSecretPath == "" {
|
||||
// startlog.Fatalf("CLIENT_ID_FILE and CLIENT_SECRET_FILE must be set")
|
||||
// }
|
||||
// clientID, err := os.ReadFile(clientIDPath)
|
||||
// if err != nil {
|
||||
// startlog.Fatalf("reading client ID %q: %v", clientIDPath, err)
|
||||
// }
|
||||
// clientSecret, err := os.ReadFile(clientSecretPath)
|
||||
// if err != nil {
|
||||
// startlog.Fatalf("reading client secret %q: %v", clientSecretPath, err)
|
||||
// }
|
||||
// credentials := clientcredentials.Config{
|
||||
// ClientID: string(clientID),
|
||||
// ClientSecret: string(clientSecret),
|
||||
// TokenURL: "https://login.tailscale.com/api/v2/oauth/token",
|
||||
// }
|
||||
// tsClient := tailscale.NewClient("-", nil)
|
||||
// tsClient.HTTPClient = credentials.Client(context.Background())
|
||||
|
||||
s := &tsnet.Server{
|
||||
Hostname: hostname,
|
||||
@@ -162,21 +164,22 @@ waitOnline:
|
||||
if loginDone {
|
||||
break
|
||||
}
|
||||
caps := tailscale.KeyCapabilities{
|
||||
Devices: tailscale.KeyDeviceCapabilities{
|
||||
Create: tailscale.KeyDeviceCreateCapabilities{
|
||||
Reusable: false,
|
||||
Preauthorized: true,
|
||||
Tags: strings.Split(operatorTags, ","),
|
||||
},
|
||||
},
|
||||
}
|
||||
authkey, _, err := tsClient.CreateKey(ctx, caps)
|
||||
if err != nil {
|
||||
startlog.Fatalf("creating operator authkey: %v", err)
|
||||
}
|
||||
// caps := tailscale.KeyCapabilities{
|
||||
// Devices: tailscale.KeyDeviceCapabilities{
|
||||
// Create: tailscale.KeyDeviceCreateCapabilities{
|
||||
// Reusable: false,
|
||||
// Preauthorized: true,
|
||||
// Tags: strings.Split(operatorTags, ","),
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// authkey := ""
|
||||
// authkey, _, err := tsClient.CreateKey(ctx, caps)
|
||||
// if err != nil {
|
||||
// startlog.Fatalf("creating operator authkey: %v", err)
|
||||
// }
|
||||
if err := lc.Start(ctx, ipn.Options{
|
||||
AuthKey: authkey,
|
||||
// AuthKey: authkey,
|
||||
}); err != nil {
|
||||
startlog.Fatalf("starting tailscale: %v", err)
|
||||
}
|
||||
@@ -195,12 +198,12 @@ waitOnline:
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
return s, tsClient
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// runReconcilers starts the controller-runtime manager and registers the
|
||||
// ServiceReconciler. It blocks forever.
|
||||
func runReconcilers(zlog *zap.SugaredLogger, s *tsnet.Server, tsNamespace string, restConfig *rest.Config, tsClient *tailscale.Client, image, priorityClassName, tags, tsFirewallMode string) {
|
||||
func runReconcilers(zlog *zap.SugaredLogger, s *tsnet.Server, tsNamespace string, restConfig *rest.Config, tsClient *tailscale.Client, image, priorityClassName, tags, tsFirewallMode string, enableConnector bool) {
|
||||
var (
|
||||
isDefaultLoadBalancer = defaultBool("OPERATOR_DEFAULT_LOAD_BALANCER", false)
|
||||
)
|
||||
@@ -215,16 +218,15 @@ func runReconcilers(zlog *zap.SugaredLogger, s *tsnet.Server, tsNamespace string
|
||||
Field: client.InNamespace(tsNamespace).AsSelector(),
|
||||
}
|
||||
mgrOpts := manager.Options{
|
||||
// TODO (irbekrm): stricter filtering what we watch/cache/call
|
||||
// reconcilers on. c/r by default starts a watch on any
|
||||
// resources that we GET via the controller manager's client.
|
||||
Cache: cache.Options{
|
||||
ByObject: map[client.Object]cache.ByObject{
|
||||
&corev1.Secret{}: nsFilter,
|
||||
&appsv1.StatefulSet{}: nsFilter,
|
||||
},
|
||||
},
|
||||
Scheme: tsapi.GlobalScheme,
|
||||
}
|
||||
if enableConnector {
|
||||
mgrOpts.Scheme = tsapi.GlobalScheme
|
||||
}
|
||||
mgr, err := manager.New(restConfig, mgrOpts)
|
||||
if err != nil {
|
||||
@@ -278,20 +280,22 @@ func runReconcilers(zlog *zap.SugaredLogger, s *tsnet.Server, tsNamespace string
|
||||
startlog.Fatalf("could not create controller: %v", err)
|
||||
}
|
||||
|
||||
connectorFilter := handler.EnqueueRequestsFromMapFunc(managedResourceHandlerForType("connector"))
|
||||
err = builder.ControllerManagedBy(mgr).
|
||||
For(&tsapi.Connector{}).
|
||||
Watches(&appsv1.StatefulSet{}, connectorFilter).
|
||||
Watches(&corev1.Secret{}, connectorFilter).
|
||||
Complete(&ConnectorReconciler{
|
||||
ssr: ssr,
|
||||
recorder: eventRecorder,
|
||||
Client: mgr.GetClient(),
|
||||
logger: zlog.Named("connector-reconciler"),
|
||||
clock: tstime.DefaultClock{},
|
||||
})
|
||||
if err != nil {
|
||||
startlog.Fatal("could not create connector reconciler: %v", err)
|
||||
if enableConnector {
|
||||
connectorFilter := handler.EnqueueRequestsFromMapFunc(managedResourceHandlerForType("subnetrouter"))
|
||||
err = builder.ControllerManagedBy(mgr).
|
||||
For(&tsapi.Connector{}).
|
||||
Watches(&appsv1.StatefulSet{}, connectorFilter).
|
||||
Watches(&corev1.Secret{}, connectorFilter).
|
||||
Complete(&ConnectorReconciler{
|
||||
ssr: ssr,
|
||||
recorder: eventRecorder,
|
||||
Client: mgr.GetClient(),
|
||||
logger: zlog.Named("connector-reconciler"),
|
||||
clock: tstime.DefaultClock{},
|
||||
})
|
||||
if err != nil {
|
||||
startlog.Fatal("could not create connector reconciler: %v", err)
|
||||
}
|
||||
}
|
||||
startlog.Infof("Startup complete, operator running, version: %s", version.Long())
|
||||
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
|
||||
|
||||
@@ -6,15 +6,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"go.uber.org/zap"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/types/ptr"
|
||||
)
|
||||
|
||||
@@ -58,18 +67,15 @@ func TestLoadBalancerClass(t *testing.T) {
|
||||
expectReconciled(t, sr, "default", "test")
|
||||
|
||||
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
||||
opts := configOpts{
|
||||
stsName: shortName,
|
||||
secretName: fullName,
|
||||
namespace: "default",
|
||||
parentType: "svc",
|
||||
hostname: "default-test",
|
||||
clusterTargetIP: "10.20.30.40",
|
||||
}
|
||||
|
||||
expectEqual(t, fc, expectedSecret(t, opts))
|
||||
expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
expectEqual(t, fc, expectedSTS(opts))
|
||||
o := stsOpts{
|
||||
name: shortName,
|
||||
secretName: fullName,
|
||||
hostname: "default-test",
|
||||
}
|
||||
expectEqual(t, fc, expectedSTS(o))
|
||||
|
||||
// Normally the Tailscale proxy pod would come up here and write its info
|
||||
// into the secret. Simulate that, then verify reconcile again and verify
|
||||
@@ -153,7 +159,6 @@ func TestLoadBalancerClass(t *testing.T) {
|
||||
}
|
||||
expectEqual(t, fc, want)
|
||||
}
|
||||
|
||||
func TestTailnetTargetFQDNAnnotation(t *testing.T) {
|
||||
fc := fake.NewFakeClient()
|
||||
ft := &fakeTSClient{}
|
||||
@@ -199,17 +204,15 @@ func TestTailnetTargetFQDNAnnotation(t *testing.T) {
|
||||
expectReconciled(t, sr, "default", "test")
|
||||
|
||||
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
||||
o := configOpts{
|
||||
stsName: shortName,
|
||||
|
||||
expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
o := stsOpts{
|
||||
name: shortName,
|
||||
secretName: fullName,
|
||||
namespace: "default",
|
||||
parentType: "svc",
|
||||
tailnetTargetFQDN: tailnetTargetFQDN,
|
||||
hostname: "default-test",
|
||||
}
|
||||
|
||||
expectEqual(t, fc, expectedSecret(t, o))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
expectEqual(t, fc, expectedSTS(o))
|
||||
want := &corev1.Service{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
@@ -232,8 +235,14 @@ func TestTailnetTargetFQDNAnnotation(t *testing.T) {
|
||||
},
|
||||
}
|
||||
expectEqual(t, fc, want)
|
||||
expectEqual(t, fc, expectedSecret(t, o))
|
||||
expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
o = stsOpts{
|
||||
name: shortName,
|
||||
secretName: fullName,
|
||||
tailnetTargetFQDN: tailnetTargetFQDN,
|
||||
hostname: "default-test",
|
||||
}
|
||||
expectEqual(t, fc, expectedSTS(o))
|
||||
|
||||
// Change the tailscale-target-fqdn annotation which should update the
|
||||
@@ -263,7 +272,6 @@ func TestTailnetTargetFQDNAnnotation(t *testing.T) {
|
||||
expectMissing[corev1.Service](t, fc, "operator-ns", shortName)
|
||||
expectMissing[corev1.Secret](t, fc, "operator-ns", fullName)
|
||||
}
|
||||
|
||||
func TestTailnetTargetIPAnnotation(t *testing.T) {
|
||||
fc := fake.NewFakeClient()
|
||||
ft := &fakeTSClient{}
|
||||
@@ -309,17 +317,15 @@ func TestTailnetTargetIPAnnotation(t *testing.T) {
|
||||
expectReconciled(t, sr, "default", "test")
|
||||
|
||||
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
||||
o := configOpts{
|
||||
stsName: shortName,
|
||||
|
||||
expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
o := stsOpts{
|
||||
name: shortName,
|
||||
secretName: fullName,
|
||||
namespace: "default",
|
||||
parentType: "svc",
|
||||
tailnetTargetIP: tailnetTargetIP,
|
||||
hostname: "default-test",
|
||||
}
|
||||
|
||||
expectEqual(t, fc, expectedSecret(t, o))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
expectEqual(t, fc, expectedSTS(o))
|
||||
want := &corev1.Service{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
@@ -342,8 +348,14 @@ func TestTailnetTargetIPAnnotation(t *testing.T) {
|
||||
},
|
||||
}
|
||||
expectEqual(t, fc, want)
|
||||
expectEqual(t, fc, expectedSecret(t, o))
|
||||
expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
o = stsOpts{
|
||||
name: shortName,
|
||||
secretName: fullName,
|
||||
tailnetTargetIP: tailnetTargetIP,
|
||||
hostname: "default-test",
|
||||
}
|
||||
expectEqual(t, fc, expectedSTS(o))
|
||||
|
||||
// Change the tailscale-target-ip annotation which should update the
|
||||
@@ -416,17 +428,14 @@ func TestAnnotations(t *testing.T) {
|
||||
expectReconciled(t, sr, "default", "test")
|
||||
|
||||
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
||||
o := configOpts{
|
||||
stsName: shortName,
|
||||
secretName: fullName,
|
||||
namespace: "default",
|
||||
parentType: "svc",
|
||||
hostname: "default-test",
|
||||
clusterTargetIP: "10.20.30.40",
|
||||
}
|
||||
|
||||
expectEqual(t, fc, expectedSecret(t, o))
|
||||
expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
o := stsOpts{
|
||||
name: shortName,
|
||||
secretName: fullName,
|
||||
hostname: "default-test",
|
||||
}
|
||||
expectEqual(t, fc, expectedSTS(o))
|
||||
want := &corev1.Service{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
@@ -524,17 +533,14 @@ func TestAnnotationIntoLB(t *testing.T) {
|
||||
expectReconciled(t, sr, "default", "test")
|
||||
|
||||
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
||||
o := configOpts{
|
||||
stsName: shortName,
|
||||
secretName: fullName,
|
||||
namespace: "default",
|
||||
parentType: "svc",
|
||||
hostname: "default-test",
|
||||
clusterTargetIP: "10.20.30.40",
|
||||
}
|
||||
|
||||
expectEqual(t, fc, expectedSecret(t, o))
|
||||
expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
o := stsOpts{
|
||||
name: shortName,
|
||||
secretName: fullName,
|
||||
hostname: "default-test",
|
||||
}
|
||||
expectEqual(t, fc, expectedSTS(o))
|
||||
|
||||
// Normally the Tailscale proxy pod would come up here and write its info
|
||||
@@ -580,6 +586,11 @@ func TestAnnotationIntoLB(t *testing.T) {
|
||||
expectReconciled(t, sr, "default", "test")
|
||||
// None of the proxy machinery should have changed...
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
o = stsOpts{
|
||||
name: shortName,
|
||||
secretName: fullName,
|
||||
hostname: "default-test",
|
||||
}
|
||||
expectEqual(t, fc, expectedSTS(o))
|
||||
// ... but the service should have a LoadBalancer status.
|
||||
|
||||
@@ -655,17 +666,14 @@ func TestLBIntoAnnotation(t *testing.T) {
|
||||
expectReconciled(t, sr, "default", "test")
|
||||
|
||||
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
||||
o := configOpts{
|
||||
stsName: shortName,
|
||||
secretName: fullName,
|
||||
namespace: "default",
|
||||
parentType: "svc",
|
||||
hostname: "default-test",
|
||||
clusterTargetIP: "10.20.30.40",
|
||||
}
|
||||
|
||||
expectEqual(t, fc, expectedSecret(t, o))
|
||||
expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
o := stsOpts{
|
||||
name: shortName,
|
||||
secretName: fullName,
|
||||
hostname: "default-test",
|
||||
}
|
||||
expectEqual(t, fc, expectedSTS(o))
|
||||
|
||||
// Normally the Tailscale proxy pod would come up here and write its info
|
||||
@@ -729,6 +737,11 @@ func TestLBIntoAnnotation(t *testing.T) {
|
||||
expectReconciled(t, sr, "default", "test")
|
||||
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
o = stsOpts{
|
||||
name: shortName,
|
||||
secretName: fullName,
|
||||
hostname: "default-test",
|
||||
}
|
||||
expectEqual(t, fc, expectedSTS(o))
|
||||
|
||||
want = &corev1.Service{
|
||||
@@ -796,17 +809,14 @@ func TestCustomHostname(t *testing.T) {
|
||||
expectReconciled(t, sr, "default", "test")
|
||||
|
||||
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
||||
o := configOpts{
|
||||
stsName: shortName,
|
||||
secretName: fullName,
|
||||
namespace: "default",
|
||||
parentType: "svc",
|
||||
hostname: "reindeer-flotilla",
|
||||
clusterTargetIP: "10.20.30.40",
|
||||
}
|
||||
|
||||
expectEqual(t, fc, expectedSecret(t, o))
|
||||
expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
o := stsOpts{
|
||||
name: shortName,
|
||||
secretName: fullName,
|
||||
hostname: "reindeer-flotilla",
|
||||
}
|
||||
expectEqual(t, fc, expectedSTS(o))
|
||||
want := &corev1.Service{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
@@ -910,14 +920,11 @@ func TestCustomPriorityClassName(t *testing.T) {
|
||||
expectReconciled(t, sr, "default", "test")
|
||||
|
||||
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
||||
o := configOpts{
|
||||
stsName: shortName,
|
||||
o := stsOpts{
|
||||
name: shortName,
|
||||
secretName: fullName,
|
||||
namespace: "default",
|
||||
parentType: "svc",
|
||||
hostname: "tailscale-critical",
|
||||
priorityClassName: "custom-priority-class-name",
|
||||
clusterTargetIP: "10.20.30.40",
|
||||
}
|
||||
|
||||
expectEqual(t, fc, expectedSTS(o))
|
||||
@@ -964,14 +971,12 @@ func TestDefaultLoadBalancer(t *testing.T) {
|
||||
|
||||
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
||||
|
||||
// expectEqual(t, fc, expectedSecret(fullName, "default", "svc"))
|
||||
expectEqual(t, fc, expectedHeadlessService(shortName))
|
||||
o := configOpts{
|
||||
stsName: shortName,
|
||||
secretName: fullName,
|
||||
namespace: "default",
|
||||
parentType: "svc",
|
||||
hostname: "default-test",
|
||||
clusterTargetIP: "10.20.30.40",
|
||||
o := stsOpts{
|
||||
name: shortName,
|
||||
secretName: fullName,
|
||||
hostname: "default-test",
|
||||
}
|
||||
expectEqual(t, fc, expectedSTS(o))
|
||||
}
|
||||
@@ -1017,19 +1022,331 @@ func TestProxyFirewallMode(t *testing.T) {
|
||||
expectReconciled(t, sr, "default", "test")
|
||||
|
||||
fullName, shortName := findGenName(t, fc, "default", "test", "svc")
|
||||
o := configOpts{
|
||||
stsName: shortName,
|
||||
secretName: fullName,
|
||||
namespace: "default",
|
||||
parentType: "svc",
|
||||
hostname: "default-test",
|
||||
firewallMode: "nftables",
|
||||
clusterTargetIP: "10.20.30.40",
|
||||
o := stsOpts{
|
||||
name: shortName,
|
||||
secretName: fullName,
|
||||
hostname: "default-test",
|
||||
firewallMode: "nftables",
|
||||
}
|
||||
expectEqual(t, fc, expectedSTS(o))
|
||||
|
||||
}
|
||||
|
||||
func expectedSecret(name, parentNamespace, typ string) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: "operator-ns",
|
||||
Labels: map[string]string{
|
||||
"tailscale.com/managed": "true",
|
||||
"tailscale.com/parent-resource": "test",
|
||||
"tailscale.com/parent-resource-ns": parentNamespace,
|
||||
"tailscale.com/parent-resource-type": typ,
|
||||
},
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"authkey": "secret-authkey",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func expectedHeadlessService(name string) *corev1.Service {
|
||||
return &corev1.Service{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Service",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
GenerateName: "ts-test-",
|
||||
Namespace: "operator-ns",
|
||||
Labels: map[string]string{
|
||||
"tailscale.com/managed": "true",
|
||||
"tailscale.com/parent-resource": "test",
|
||||
"tailscale.com/parent-resource-ns": "default",
|
||||
"tailscale.com/parent-resource-type": "svc",
|
||||
},
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Selector: map[string]string{
|
||||
"app": "1234-UID",
|
||||
},
|
||||
ClusterIP: "None",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func expectedSTS(opts stsOpts) *appsv1.StatefulSet {
|
||||
containerEnv := []corev1.EnvVar{
|
||||
{Name: "TS_USERSPACE", Value: "false"},
|
||||
{Name: "TS_AUTH_ONCE", Value: "true"},
|
||||
{Name: "TS_KUBE_SECRET", Value: opts.secretName},
|
||||
{Name: "TS_HOSTNAME", Value: opts.hostname},
|
||||
}
|
||||
annots := map[string]string{
|
||||
"tailscale.com/operator-last-set-hostname": opts.hostname,
|
||||
}
|
||||
if opts.tailnetTargetIP != "" {
|
||||
annots["tailscale.com/operator-last-set-ts-tailnet-target-ip"] = opts.tailnetTargetIP
|
||||
containerEnv = append(containerEnv, corev1.EnvVar{
|
||||
Name: "TS_TAILNET_TARGET_IP",
|
||||
Value: opts.tailnetTargetIP,
|
||||
})
|
||||
} else if opts.tailnetTargetFQDN != "" {
|
||||
annots["tailscale.com/operator-last-set-ts-tailnet-target-fqdn"] = opts.tailnetTargetFQDN
|
||||
containerEnv = append(containerEnv, corev1.EnvVar{
|
||||
Name: "TS_TAILNET_TARGET_FQDN",
|
||||
Value: opts.tailnetTargetFQDN,
|
||||
})
|
||||
|
||||
} else {
|
||||
containerEnv = append(containerEnv, corev1.EnvVar{
|
||||
Name: "TS_DEST_IP",
|
||||
Value: "10.20.30.40",
|
||||
})
|
||||
|
||||
annots["tailscale.com/operator-last-set-cluster-ip"] = "10.20.30.40"
|
||||
|
||||
}
|
||||
if opts.firewallMode != "" {
|
||||
containerEnv = append(containerEnv, corev1.EnvVar{
|
||||
Name: "TS_DEBUG_FIREWALL_MODE",
|
||||
Value: opts.firewallMode,
|
||||
})
|
||||
}
|
||||
return &appsv1.StatefulSet{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "StatefulSet",
|
||||
APIVersion: "apps/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: opts.name,
|
||||
Namespace: "operator-ns",
|
||||
Labels: map[string]string{
|
||||
"tailscale.com/managed": "true",
|
||||
"tailscale.com/parent-resource": "test",
|
||||
"tailscale.com/parent-resource-ns": "default",
|
||||
"tailscale.com/parent-resource-type": "svc",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
Replicas: ptr.To[int32](1),
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"app": "1234-UID"},
|
||||
},
|
||||
ServiceName: opts.name,
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: annots,
|
||||
DeletionGracePeriodSeconds: ptr.To[int64](10),
|
||||
Labels: map[string]string{"app": "1234-UID"},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
ServiceAccountName: "proxies",
|
||||
PriorityClassName: opts.priorityClassName,
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "sysctler",
|
||||
Image: "tailscale/tailscale",
|
||||
Command: []string{"/bin/sh"},
|
||||
Args: []string{"-c", "sysctl -w net.ipv4.ip_forward=1 net.ipv6.conf.all.forwarding=1"},
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "tailscale",
|
||||
Image: "tailscale/tailscale",
|
||||
Env: containerEnv,
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"NET_ADMIN"},
|
||||
},
|
||||
},
|
||||
ImagePullPolicy: "Always",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func findGenName(t *testing.T, client client.Client, ns, name, typ string) (full, noSuffix string) {
|
||||
t.Helper()
|
||||
labels := map[string]string{
|
||||
LabelManaged: "true",
|
||||
LabelParentName: name,
|
||||
LabelParentNamespace: ns,
|
||||
LabelParentType: typ,
|
||||
}
|
||||
s, err := getSingleObject[corev1.Secret](context.Background(), client, "operator-ns", labels)
|
||||
if err != nil {
|
||||
t.Fatalf("finding secret for %q: %v", name, err)
|
||||
}
|
||||
if s == nil {
|
||||
t.Fatalf("no secret found for %q %s %+#v", name, ns, labels)
|
||||
}
|
||||
return s.GetName(), strings.TrimSuffix(s.GetName(), "-0")
|
||||
}
|
||||
|
||||
func mustCreate(t *testing.T, client client.Client, obj client.Object) {
|
||||
t.Helper()
|
||||
if err := client.Create(context.Background(), obj); err != nil {
|
||||
t.Fatalf("creating %q: %v", obj.GetName(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func mustUpdate[T any, O ptrObject[T]](t *testing.T, client client.Client, ns, name string, update func(O)) {
|
||||
t.Helper()
|
||||
obj := O(new(T))
|
||||
if err := client.Get(context.Background(), types.NamespacedName{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
}, obj); err != nil {
|
||||
t.Fatalf("getting %q: %v", name, err)
|
||||
}
|
||||
update(obj)
|
||||
if err := client.Update(context.Background(), obj); err != nil {
|
||||
t.Fatalf("updating %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func mustUpdateStatus[T any, O ptrObject[T]](t *testing.T, client client.Client, ns, name string, update func(O)) {
|
||||
t.Helper()
|
||||
obj := O(new(T))
|
||||
if err := client.Get(context.Background(), types.NamespacedName{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
}, obj); err != nil {
|
||||
t.Fatalf("getting %q: %v", name, err)
|
||||
}
|
||||
update(obj)
|
||||
if err := client.Status().Update(context.Background(), obj); err != nil {
|
||||
t.Fatalf("updating %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func expectEqual[T any, O ptrObject[T]](t *testing.T, client client.Client, want O) {
|
||||
t.Helper()
|
||||
got := O(new(T))
|
||||
if err := client.Get(context.Background(), types.NamespacedName{
|
||||
Name: want.GetName(),
|
||||
Namespace: want.GetNamespace(),
|
||||
}, got); err != nil {
|
||||
t.Fatalf("getting %q: %v", want.GetName(), err)
|
||||
}
|
||||
// The resource version changes eagerly whenever the operator does even a
|
||||
// no-op update. Asserting a specific value leads to overly brittle tests,
|
||||
// so just remove it from both got and want.
|
||||
got.SetResourceVersion("")
|
||||
want.SetResourceVersion("")
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Fatalf("unexpected object (-got +want):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func expectMissing[T any, O ptrObject[T]](t *testing.T, client client.Client, ns, name string) {
|
||||
t.Helper()
|
||||
obj := O(new(T))
|
||||
if err := client.Get(context.Background(), types.NamespacedName{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
}, obj); !apierrors.IsNotFound(err) {
|
||||
t.Fatalf("object %s/%s unexpectedly present, wanted missing", ns, name)
|
||||
}
|
||||
}
|
||||
|
||||
func expectReconciled(t *testing.T, sr reconcile.Reconciler, ns, name string) {
|
||||
t.Helper()
|
||||
req := reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: ns,
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
res, err := sr.Reconcile(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Reconcile: unexpected error: %v", err)
|
||||
}
|
||||
if res.Requeue {
|
||||
t.Fatalf("unexpected immediate requeue")
|
||||
}
|
||||
if res.RequeueAfter != 0 {
|
||||
t.Fatalf("unexpected timed requeue (%v)", res.RequeueAfter)
|
||||
}
|
||||
}
|
||||
|
||||
func expectRequeue(t *testing.T, sr reconcile.Reconciler, ns, name string) {
|
||||
t.Helper()
|
||||
req := reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
},
|
||||
}
|
||||
res, err := sr.Reconcile(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Reconcile: unexpected error: %v", err)
|
||||
}
|
||||
if res.RequeueAfter == 0 {
|
||||
t.Fatalf("expected timed requeue, got success")
|
||||
}
|
||||
}
|
||||
|
||||
type stsOpts struct {
|
||||
name string
|
||||
secretName string
|
||||
hostname string
|
||||
priorityClassName string
|
||||
firewallMode string
|
||||
tailnetTargetIP string
|
||||
tailnetTargetFQDN string
|
||||
}
|
||||
|
||||
type fakeTSClient struct {
|
||||
sync.Mutex
|
||||
keyRequests []tailscale.KeyCapabilities
|
||||
deleted []string
|
||||
}
|
||||
|
||||
func (c *fakeTSClient) CreateKey(ctx context.Context, caps tailscale.KeyCapabilities) (string, *tailscale.Key, error) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.keyRequests = append(c.keyRequests, caps)
|
||||
k := &tailscale.Key{
|
||||
ID: "key",
|
||||
Created: time.Now(),
|
||||
Capabilities: caps,
|
||||
}
|
||||
return "secret-authkey", k, nil
|
||||
}
|
||||
|
||||
func (c *fakeTSClient) DeleteDevice(ctx context.Context, deviceID string) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.deleted = append(c.deleted, deviceID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeTSClient) KeyRequests() []tailscale.KeyCapabilities {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
return c.keyRequests
|
||||
}
|
||||
|
||||
func (c *fakeTSClient) Deleted() []string {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
return c.deleted
|
||||
}
|
||||
|
||||
func Test_isMagicDNSName(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
|
||||
@@ -6,15 +6,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/transport"
|
||||
@@ -23,11 +33,22 @@ import (
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tsnet"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/ctxkey"
|
||||
"tailscale.com/util/set"
|
||||
)
|
||||
|
||||
var whoIsKey = ctxkey.New("", (*apitype.WhoIsResponse)(nil))
|
||||
type whoIsKey struct{}
|
||||
|
||||
// whoIsFromRequest returns the WhoIsResponse previously stashed by a call to
|
||||
// addWhoIsToRequest.
|
||||
func whoIsFromRequest(r *http.Request) *apitype.WhoIsResponse {
|
||||
return r.Context().Value(whoIsKey{}).(*apitype.WhoIsResponse)
|
||||
}
|
||||
|
||||
// addWhoIsToRequest stashes who in r's context, retrievable by a call to
|
||||
// whoIsFromRequest.
|
||||
func addWhoIsToRequest(r *http.Request, who *apitype.WhoIsResponse) *http.Request {
|
||||
return r.WithContext(context.WithValue(r.Context(), whoIsKey{}, who))
|
||||
}
|
||||
|
||||
var counterNumRequestsProxied = clientmetric.NewCounter("k8s_auth_proxy_requests_proxied")
|
||||
|
||||
@@ -75,9 +96,9 @@ func maybeLaunchAPIServerProxy(zlog *zap.SugaredLogger, restConfig *rest.Config,
|
||||
return
|
||||
}
|
||||
startlog := zlog.Named("launchAPIProxy")
|
||||
if mode == apiserverProxyModeNoAuth {
|
||||
restConfig = rest.AnonymousClientConfig(restConfig)
|
||||
}
|
||||
// if mode == apiserverProxyModeNoAuth {
|
||||
// restConfig = rest.AnonymousClientConfig(restConfig)
|
||||
// }
|
||||
cfg, err := restConfig.TransportConfig()
|
||||
if err != nil {
|
||||
startlog.Fatalf("could not get rest.TransportConfig(): %v", err)
|
||||
@@ -96,15 +117,89 @@ func maybeLaunchAPIServerProxy(zlog *zap.SugaredLogger, restConfig *rest.Config,
|
||||
if err != nil {
|
||||
startlog.Fatalf("could not get rest.TransportConfig(): %v", err)
|
||||
}
|
||||
go runAPIServerProxy(s, rt, zlog.Named("apiserver-proxy"), mode)
|
||||
go runAPIServerProxy(s, rt, zlog.Named("apiserver-proxy"), mode, restConfig.Host)
|
||||
}
|
||||
|
||||
// apiserverProxy is an http.Handler that authenticates requests using the Tailscale
|
||||
// LocalAPI and then proxies them to the Kubernetes API.
|
||||
type apiserverProxy struct {
|
||||
log *zap.SugaredLogger
|
||||
lc *tailscale.LocalClient
|
||||
rp *httputil.ReverseProxy
|
||||
log *zap.SugaredLogger
|
||||
lc *tailscale.LocalClient
|
||||
rp *httputil.ReverseProxy
|
||||
mode apiServerProxyMode
|
||||
|
||||
upstreamURL *url.URL
|
||||
upstreamClient *http.Client
|
||||
}
|
||||
|
||||
// Hop-by-hop headers. These are removed when sent to the backend.
|
||||
// As of RFC 7230, hop-by-hop headers are required to appear in the
|
||||
// Connection header field. These are the headers defined by the
|
||||
// obsoleted RFC 2616 (section 13.5.1) and are used for backward
|
||||
// compatibility.
|
||||
var hopHeaders = []string{
|
||||
"Connection",
|
||||
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
|
||||
"Keep-Alive",
|
||||
"Proxy-Authenticate",
|
||||
"Proxy-Authorization",
|
||||
"Te", // canonicalized version of "TE"
|
||||
"Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522
|
||||
"Transfer-Encoding",
|
||||
"Upgrade",
|
||||
}
|
||||
|
||||
// removeHopByHopHeaders removes hop-by-hop headers.
|
||||
func removeHopByHopHeaders(h http.Header) {
|
||||
// RFC 7230, section 6.1: Remove headers listed in the "Connection" header.
|
||||
for _, f := range h["Connection"] {
|
||||
for _, sf := range strings.Split(f, ",") {
|
||||
if sf = textproto.TrimString(sf); sf != "" {
|
||||
h.Del(sf)
|
||||
}
|
||||
}
|
||||
}
|
||||
// RFC 2616, section 13.5.1: Remove a set of known hop-by-hop headers.
|
||||
// This behavior is superseded by the RFC 7230 Connection header, but
|
||||
// preserve it for backwards compatibility.
|
||||
for _, f := range hopHeaders {
|
||||
h.Del(f)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *apiserverProxy) addImpersonationHeadersAsRequired(r *http.Request) {
|
||||
// Replace the URL with the Kubernetes APIServer.
|
||||
|
||||
r.URL.Scheme = h.upstreamURL.Scheme
|
||||
r.URL.Host = h.upstreamURL.Host
|
||||
if h.mode == apiserverProxyModeNoAuth {
|
||||
// If we are not providing authentication, then we are just
|
||||
// proxying to the Kubernetes API, so we don't need to do
|
||||
// anything else.
|
||||
return
|
||||
}
|
||||
|
||||
// We want to proxy to the Kubernetes API, but we want to use
|
||||
// the caller's identity to do so. We do this by impersonating
|
||||
// the caller using the Kubernetes User Impersonation feature:
|
||||
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation
|
||||
|
||||
// Out of paranoia, remove all authentication headers that might
|
||||
// have been set by the client.
|
||||
r.Header.Del("Authorization")
|
||||
r.Header.Del("Impersonate-Group")
|
||||
r.Header.Del("Impersonate-User")
|
||||
r.Header.Del("Impersonate-Uid")
|
||||
for k := range r.Header {
|
||||
if strings.HasPrefix(k, "Impersonate-Extra-") {
|
||||
r.Header.Del(k)
|
||||
}
|
||||
}
|
||||
|
||||
// Now add the impersonation headers that we want.
|
||||
if err := addImpersonationHeaders(r, h.log); err != nil {
|
||||
panic("failed to add impersonation headers: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (h *apiserverProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -115,7 +210,84 @@ func (h *apiserverProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
counterNumRequestsProxied.Add(1)
|
||||
h.rp.ServeHTTP(w, r.WithContext(whoIsKey.WithValue(r.Context(), who)))
|
||||
r = addWhoIsToRequest(r, who)
|
||||
if r.Method != "POST" || path.Base(r.URL.Path) != "exec" { // also check for pod
|
||||
h.rp.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
// hj := w.(http.Hijacker)
|
||||
// reqConn, brw, err := hj.Hijack()
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// defer reqConn.Close()
|
||||
// if err := brw.Flush(); err != nil {
|
||||
// return
|
||||
// }
|
||||
// reqConn = netutil.NewDrainBufConn(reqConn, brw.Reader)
|
||||
// respConn, err := net.Dial("tcp", h.upstreamURL.Host)
|
||||
// if err != nil {
|
||||
// h.log.Errorf("failed to dial upstream: %v", err)
|
||||
// return
|
||||
// }
|
||||
// defer respConn.Close()
|
||||
|
||||
req2 := r.Clone(r.Context())
|
||||
h.addImpersonationHeadersAsRequired(req2)
|
||||
|
||||
req2.Body = io.NopCloser(io.TeeReader(r.Body, os.Stdout))
|
||||
defer r.Body.Close()
|
||||
|
||||
h.rp.ServeHTTP(&teeResponseWriter{
|
||||
ResponseWriter: w,
|
||||
hj: w.(http.Hijacker),
|
||||
multiWriter: io.MultiWriter(os.Stdout, w),
|
||||
}, req2)
|
||||
}
|
||||
|
||||
type teeResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
hj http.Hijacker
|
||||
multiWriter io.Writer
|
||||
}
|
||||
|
||||
func (w *teeResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
reqConn, brw, err := w.hj.Hijack()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
f, err := os.OpenFile("/tmp/recording.cast", os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
r := &recording{
|
||||
start: time.Now(),
|
||||
failOpen: true,
|
||||
out: f,
|
||||
}
|
||||
lc := &loggingConn{Conn: reqConn, lw: &loggingWriter{
|
||||
r: r,
|
||||
recordingFailedOpen: false,
|
||||
}}
|
||||
|
||||
ch := CastHeader{
|
||||
Version: 2,
|
||||
Timestamp: r.start.Unix(),
|
||||
}
|
||||
j, err := json.Marshal(ch)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
j = append(j, '\n')
|
||||
if _, err := f.Write(j); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return lc, brw, nil
|
||||
}
|
||||
|
||||
func (w *teeResponseWriter) Write(b []byte) (int, error) {
|
||||
return w.multiWriter.Write(b)
|
||||
}
|
||||
|
||||
// runAPIServerProxy runs an HTTP server that authenticates requests using the
|
||||
@@ -132,7 +304,7 @@ func (h *apiserverProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// are passed through to the Kubernetes API.
|
||||
//
|
||||
// It never returns.
|
||||
func runAPIServerProxy(s *tsnet.Server, rt http.RoundTripper, log *zap.SugaredLogger, mode apiServerProxyMode) {
|
||||
func runAPIServerProxy(s *tsnet.Server, rt http.RoundTripper, log *zap.SugaredLogger, mode apiServerProxyMode, host string) {
|
||||
if mode == apiserverProxyModeDisabled {
|
||||
return
|
||||
}
|
||||
@@ -140,7 +312,7 @@ func runAPIServerProxy(s *tsnet.Server, rt http.RoundTripper, log *zap.SugaredLo
|
||||
if err != nil {
|
||||
log.Fatalf("could not listen on :443: %v", err)
|
||||
}
|
||||
u, err := url.Parse(fmt.Sprintf("https://%s:%s", os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT_HTTPS")))
|
||||
u, err := url.Parse(host)
|
||||
if err != nil {
|
||||
log.Fatalf("runAPIServerProxy: failed to parse URL %v", err)
|
||||
}
|
||||
@@ -150,45 +322,16 @@ func runAPIServerProxy(s *tsnet.Server, rt http.RoundTripper, log *zap.SugaredLo
|
||||
log.Fatalf("could not get local client: %v", err)
|
||||
}
|
||||
ap := &apiserverProxy{
|
||||
log: log,
|
||||
lc: lc,
|
||||
rp: &httputil.ReverseProxy{
|
||||
Rewrite: func(r *httputil.ProxyRequest) {
|
||||
// Replace the URL with the Kubernetes APIServer.
|
||||
|
||||
r.Out.URL.Scheme = u.Scheme
|
||||
r.Out.URL.Host = u.Host
|
||||
if mode == apiserverProxyModeNoAuth {
|
||||
// If we are not providing authentication, then we are just
|
||||
// proxying to the Kubernetes API, so we don't need to do
|
||||
// anything else.
|
||||
return
|
||||
}
|
||||
|
||||
// We want to proxy to the Kubernetes API, but we want to use
|
||||
// the caller's identity to do so. We do this by impersonating
|
||||
// the caller using the Kubernetes User Impersonation feature:
|
||||
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation
|
||||
|
||||
// Out of paranoia, remove all authentication headers that might
|
||||
// have been set by the client.
|
||||
r.Out.Header.Del("Authorization")
|
||||
r.Out.Header.Del("Impersonate-Group")
|
||||
r.Out.Header.Del("Impersonate-User")
|
||||
r.Out.Header.Del("Impersonate-Uid")
|
||||
for k := range r.Out.Header {
|
||||
if strings.HasPrefix(k, "Impersonate-Extra-") {
|
||||
r.Out.Header.Del(k)
|
||||
}
|
||||
}
|
||||
|
||||
// Now add the impersonation headers that we want.
|
||||
if err := addImpersonationHeaders(r.Out, log); err != nil {
|
||||
panic("failed to add impersonation headers: " + err.Error())
|
||||
}
|
||||
},
|
||||
Transport: rt,
|
||||
log: log,
|
||||
lc: lc,
|
||||
mode: mode,
|
||||
upstreamURL: u,
|
||||
}
|
||||
ap.rp = &httputil.ReverseProxy{
|
||||
Rewrite: func(pr *httputil.ProxyRequest) {
|
||||
ap.addImpersonationHeadersAsRequired(pr.Out)
|
||||
},
|
||||
Transport: rt,
|
||||
}
|
||||
hs := &http.Server{
|
||||
// Kubernetes uses SPDY for exec and port-forward, however SPDY is
|
||||
@@ -228,7 +371,7 @@ type impersonateRule struct {
|
||||
// in the context by the apiserverProxy.
|
||||
func addImpersonationHeaders(r *http.Request, log *zap.SugaredLogger) error {
|
||||
log = log.With("remote", r.RemoteAddr)
|
||||
who := whoIsKey.Value(r.Context())
|
||||
who := whoIsFromRequest(r)
|
||||
rules, err := tailcfg.UnmarshalCapJSON[capRule](who.CapMap, capabilityName)
|
||||
if len(rules) == 0 && err == nil {
|
||||
// Try the old capability name for backwards compatibility.
|
||||
@@ -273,3 +416,151 @@ func addImpersonationHeaders(r *http.Request, log *zap.SugaredLogger) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CastHeader is the header of an asciinema file.
|
||||
type CastHeader struct {
|
||||
// Version is the asciinema file format version.
|
||||
Version int `json:"version"`
|
||||
|
||||
// Width is the terminal width in characters.
|
||||
// It is non-zero for Pty sessions.
|
||||
Width int `json:"width"`
|
||||
|
||||
// Height is the terminal height in characters.
|
||||
// It is non-zero for Pty sessions.
|
||||
Height int `json:"height"`
|
||||
|
||||
// Timestamp is the unix timestamp of when the recording started.
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
|
||||
// Env is the environment variables of the session.
|
||||
// Only "TERM" is set (2023-03-22).
|
||||
Env map[string]string `json:"env"`
|
||||
|
||||
// Command is the command that was executed.
|
||||
// Typically empty for shell sessions.
|
||||
Command string `json:"command,omitempty"`
|
||||
|
||||
// Tailscale-specific fields:
|
||||
// SrcNode is the FQDN of the node originating the connection.
|
||||
// It is also the MagicDNS name for the node.
|
||||
// It does not have a trailing dot.
|
||||
// e.g. "host.tail-scale.ts.net"
|
||||
SrcNode string `json:"srcNode"`
|
||||
|
||||
// SrcNodeID is the node ID of the node originating the connection.
|
||||
SrcNodeID tailcfg.StableNodeID `json:"srcNodeID"`
|
||||
|
||||
// SrcNodeTags is the list of tags on the node originating the connection (if any).
|
||||
SrcNodeTags []string `json:"srcNodeTags,omitempty"`
|
||||
|
||||
// SrcNodeUserID is the user ID of the node originating the connection (if not tagged).
|
||||
SrcNodeUserID tailcfg.UserID `json:"srcNodeUserID,omitempty"` // if not tagged
|
||||
|
||||
// SrcNodeUser is the LoginName of the node originating the connection (if not tagged).
|
||||
SrcNodeUser string `json:"srcNodeUser,omitempty"`
|
||||
|
||||
// SSHUser is the username as presented by the client.
|
||||
SSHUser string `json:"sshUser"` // as presented by the client
|
||||
|
||||
// LocalUser is the effective username on the server.
|
||||
LocalUser string `json:"localUser"`
|
||||
|
||||
// ConnectionID uniquely identifies a connection made to the SSH server.
|
||||
// It may be shared across multiple sessions over the same connection in
|
||||
// case of SSH multiplexing.
|
||||
ConnectionID string `json:"connectionID"`
|
||||
}
|
||||
|
||||
// loggingWriter is an io.Writer wrapper that writes first an
|
||||
// asciinema JSON cast format recording line, and then writes to w.
|
||||
type loggingWriter struct {
|
||||
r *recording
|
||||
|
||||
// recordingFailedOpen specifies whether we've failed to write to
|
||||
// r.out and should stop trying. It is set to true if we fail to write
|
||||
// to r.out and r.failOpen is set.
|
||||
recordingFailedOpen bool
|
||||
}
|
||||
|
||||
func (w *loggingWriter) Write(p []byte) (n int, err error) {
|
||||
if w.recordingFailedOpen {
|
||||
return 0, nil
|
||||
}
|
||||
j, err := json.Marshal([]any{
|
||||
time.Since(w.r.start).Seconds(),
|
||||
"o",
|
||||
string(p),
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
j = append(j, '\n')
|
||||
if err := w.writeCastLine(j); err != nil {
|
||||
if !w.r.failOpen {
|
||||
return 0, err
|
||||
}
|
||||
w.recordingFailedOpen = true
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (w *loggingWriter) writeCastLine(j []byte) error {
|
||||
w.r.mu.Lock()
|
||||
defer w.r.mu.Unlock()
|
||||
if w.r.out == nil {
|
||||
return errors.New("logger closed")
|
||||
}
|
||||
_, err := w.r.out.Write(j)
|
||||
if err != nil {
|
||||
return fmt.Errorf("logger Write: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type loggingConn struct {
|
||||
mu sync.Mutex // guards writes to r.out
|
||||
closed bool
|
||||
net.Conn
|
||||
lw *loggingWriter
|
||||
}
|
||||
|
||||
func (c *loggingConn) Write(b []byte) (int, error) {
|
||||
n, err := c.Conn.Write(b)
|
||||
c.lw.Write(b[:n])
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *loggingConn) Close() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.closed {
|
||||
return nil
|
||||
}
|
||||
c.closed = true
|
||||
c.lw.r.Close()
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
// recording is the state for an SSH session recording.
|
||||
type recording struct {
|
||||
start time.Time
|
||||
|
||||
// failOpen specifies whether the session should be allowed to
|
||||
// continue if writing to the recording fails.
|
||||
failOpen bool
|
||||
|
||||
mu sync.Mutex // guards writes to, close of out
|
||||
out io.WriteCloser
|
||||
}
|
||||
|
||||
func (r *recording) Close() error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.out == nil {
|
||||
return nil
|
||||
}
|
||||
err := r.out.Close()
|
||||
r.out = nil
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ func TestImpersonationHeaders(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
r := must.Get(http.NewRequest("GET", "https://op.ts.net/api/foo", nil))
|
||||
r = r.WithContext(whoIsKey.WithValue(r.Context(), &apitype.WhoIsResponse{
|
||||
r = addWhoIsToRequest(r, &apitype.WhoIsResponse{
|
||||
Node: &tailcfg.Node{
|
||||
Name: "node.ts.net",
|
||||
Tags: tc.tags,
|
||||
@@ -104,7 +104,7 @@ func TestImpersonationHeaders(t *testing.T) {
|
||||
LoginName: tc.emailish,
|
||||
},
|
||||
CapMap: tc.capMap,
|
||||
}))
|
||||
})
|
||||
addImpersonationHeaders(r, zl.Sugar())
|
||||
|
||||
if d := cmp.Diff(tc.wantHeaders, r.Header); d != "" {
|
||||
|
||||
@@ -7,11 +7,11 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"sigs.k8s.io/yaml"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tsnet"
|
||||
"tailscale.com/types/opt"
|
||||
@@ -56,17 +55,12 @@ const (
|
||||
AnnotationFunnel = "tailscale.com/funnel"
|
||||
|
||||
// Annotations set by the operator on pods to trigger restarts when the
|
||||
// hostname, IP, FQDN or tailscaled config changes.
|
||||
// hostname, IP or FQDN changes.
|
||||
podAnnotationLastSetClusterIP = "tailscale.com/operator-last-set-cluster-ip"
|
||||
podAnnotationLastSetHostname = "tailscale.com/operator-last-set-hostname"
|
||||
podAnnotationLastSetTailnetTargetIP = "tailscale.com/operator-last-set-ts-tailnet-target-ip"
|
||||
podAnnotationLastSetTailnetTargetFQDN = "tailscale.com/operator-last-set-ts-tailnet-target-fqdn"
|
||||
// podAnnotationLastSetConfigFileHash is sha256 hash of the current tailscaled configuration contents.
|
||||
podAnnotationLastSetConfigFileHash = "tailscale.com/operator-last-set-config-file-hash"
|
||||
|
||||
// tailscaledConfigKey is the name of the key in proxy Secret Data that
|
||||
// holds the tailscaled config contents.
|
||||
tailscaledConfigKey = "tailscaled"
|
||||
podAnnotationLastSetConfigFileHash = "tailscale.com/operator-last-set-config-file-hash"
|
||||
)
|
||||
|
||||
type tailscaleSTSConfig struct {
|
||||
@@ -74,12 +68,15 @@ type tailscaleSTSConfig struct {
|
||||
ParentResourceUID string
|
||||
ChildResourceLabels map[string]string
|
||||
|
||||
ServeConfig *ipn.ServeConfig
|
||||
ClusterTargetIP string // ingress target
|
||||
ServeConfig *ipn.ServeConfig
|
||||
// Tailscale target in cluster we are setting up ingress for
|
||||
ClusterTargetIP string
|
||||
|
||||
TailnetTargetIP string // egress target IP
|
||||
// Tailscale IP of a Tailscale service we are setting up egress for
|
||||
TailnetTargetIP string
|
||||
|
||||
TailnetTargetFQDN string // egress target FQDN
|
||||
// Tailscale FQDN of a Tailscale service we are setting up egress for
|
||||
TailnetTargetFQDN string
|
||||
|
||||
Hostname string
|
||||
Tags []string // if empty, use defaultTags
|
||||
@@ -87,6 +84,9 @@ type tailscaleSTSConfig struct {
|
||||
// Connector specifies a configuration of a Connector instance if that's
|
||||
// what this StatefulSet should be created for.
|
||||
Connector *connector
|
||||
|
||||
// temp fix whilst prototyping, remove
|
||||
key string
|
||||
}
|
||||
|
||||
type connector struct {
|
||||
@@ -123,17 +123,17 @@ func (a *tailscaleSTSReconciler) IsHTTPSEnabledOnTailnet() bool {
|
||||
// up to date.
|
||||
func (a *tailscaleSTSReconciler) Provision(ctx context.Context, logger *zap.SugaredLogger, sts *tailscaleSTSConfig) (*corev1.Service, error) {
|
||||
// Do full reconcile.
|
||||
// TODO (don't create Service for the Connector)
|
||||
hsvc, err := a.reconcileHeadlessService(ctx, logger, sts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to reconcile headless service: %w", err)
|
||||
}
|
||||
|
||||
secretName, tsConfigHash, err := a.createOrGetSecret(ctx, logger, sts, hsvc)
|
||||
secretName, key, err := a.createOrGetSecret(ctx, logger, sts, hsvc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create or get API key secret: %w", err)
|
||||
}
|
||||
_, err = a.reconcileSTS(ctx, logger, sts, hsvc, secretName, tsConfigHash)
|
||||
sts.key = key
|
||||
_, err = a.reconcileSTS(ctx, logger, sts, hsvc, secretName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to reconcile statefulset: %w", err)
|
||||
}
|
||||
@@ -214,17 +214,18 @@ const maxStatefulSetNameLength = 63 - 10 - 1
|
||||
// generation will NOT result in a StatefulSet name longer than 52 chars.
|
||||
// This is done because of https://github.com/kubernetes/kubernetes/issues/64023.
|
||||
func statefulSetNameBase(parent string) string {
|
||||
|
||||
base := fmt.Sprintf("ts-%s-", parent)
|
||||
|
||||
// Calculate what length name GenerateName returns for this base.
|
||||
generator := names.SimpleNameGenerator
|
||||
for {
|
||||
generatedName := generator.GenerateName(base)
|
||||
excess := len(generatedName) - maxStatefulSetNameLength
|
||||
if excess <= 0 {
|
||||
return base
|
||||
}
|
||||
base = base[:len(base)-1-excess] // cut off the excess chars
|
||||
base = base + "-" // re-instate the dash
|
||||
generatedName := generator.GenerateName(base)
|
||||
|
||||
if excess := len(generatedName) - maxStatefulSetNameLength; excess > 0 {
|
||||
base = base[:len(base)-excess-1] // take extra char off to make space for hyphen
|
||||
base = base + "-" // re-instate hyphen
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func (a *tailscaleSTSReconciler) reconcileHeadlessService(ctx context.Context, logger *zap.SugaredLogger, sts *tailscaleSTSConfig) (*corev1.Service, error) {
|
||||
@@ -265,9 +266,7 @@ func (a *tailscaleSTSReconciler) createOrGetSecret(ctx context.Context, logger *
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var (
|
||||
authKey, hash string
|
||||
)
|
||||
var authKey string
|
||||
if orig == nil {
|
||||
// Secret doesn't exist yet, create one. Initially it contains
|
||||
// only the Tailscale authkey, but once Tailscale starts it'll
|
||||
@@ -289,21 +288,14 @@ func (a *tailscaleSTSReconciler) createOrGetSecret(ctx context.Context, logger *
|
||||
if len(tags) == 0 {
|
||||
tags = a.defaultTags
|
||||
}
|
||||
authKey, err = a.newAuthKey(ctx, tags)
|
||||
authKey, err := a.newAuthKey(ctx, tags)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
if !shouldDoTailscaledDeclarativeConfig(stsC) && authKey != "" {
|
||||
|
||||
mak.Set(&secret.StringData, "authkey", authKey)
|
||||
}
|
||||
if shouldDoTailscaledDeclarativeConfig(stsC) {
|
||||
confFileBytes, h, err := tailscaledConfig(stsC, authKey, orig)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("error creating tailscaled config: %w", err)
|
||||
}
|
||||
hash = h
|
||||
mak.Set(&secret.StringData, tailscaledConfigKey, string(confFileBytes))
|
||||
} else {
|
||||
authKey = string(orig.Data["authkey"])
|
||||
}
|
||||
if stsC.ServeConfig != nil {
|
||||
j, err := json.Marshal(stsC.ServeConfig)
|
||||
@@ -312,19 +304,18 @@ func (a *tailscaleSTSReconciler) createOrGetSecret(ctx context.Context, logger *
|
||||
}
|
||||
mak.Set(&secret.StringData, "serve-config", string(j))
|
||||
}
|
||||
|
||||
if orig != nil {
|
||||
logger.Debugf("patching existing state Secret with values %s", secret.Data[tailscaledConfigKey])
|
||||
log.Printf("Patching existing secret %s", secret.Name)
|
||||
if err := a.Patch(ctx, secret, client.MergeFrom(orig)); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
} else {
|
||||
logger.Debugf("creating new state Secret with authkey %s", secret.Data[tailscaledConfigKey])
|
||||
if err := a.Create(ctx, secret); err != nil {
|
||||
return "", "", err
|
||||
return "", authKey, err
|
||||
}
|
||||
log.Printf("Created secret %s", secret.Name)
|
||||
}
|
||||
return secret.Name, hash, nil
|
||||
return secret.Name, authKey, nil
|
||||
}
|
||||
|
||||
// DeviceInfo returns the device ID and hostname for the Tailscale device
|
||||
@@ -352,6 +343,7 @@ func (a *tailscaleSTSReconciler) DeviceInfo(ctx context.Context, childLabels map
|
||||
return "", "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return id, hostname, ips, nil
|
||||
}
|
||||
|
||||
@@ -379,7 +371,7 @@ var proxyYaml []byte
|
||||
//go:embed deploy/manifests/userspace-proxy.yaml
|
||||
var userspaceProxyYaml []byte
|
||||
|
||||
func (a *tailscaleSTSReconciler) reconcileSTS(ctx context.Context, logger *zap.SugaredLogger, sts *tailscaleSTSConfig, headlessSvc *corev1.Service, proxySecret, tsConfigHash string) (*appsv1.StatefulSet, error) {
|
||||
func (a *tailscaleSTSReconciler) reconcileSTS(ctx context.Context, logger *zap.SugaredLogger, sts *tailscaleSTSConfig, headlessSvc *corev1.Service, authKeySecret string) (*appsv1.StatefulSet, error) {
|
||||
var ss appsv1.StatefulSet
|
||||
if sts.ServeConfig != nil {
|
||||
if err := yaml.Unmarshal(userspaceProxyYaml, &ss); err != nil {
|
||||
@@ -399,93 +391,32 @@ func (a *tailscaleSTSReconciler) reconcileSTS(ctx context.Context, logger *zap.S
|
||||
}
|
||||
container := &ss.Spec.Template.Spec.Containers[0]
|
||||
container.Image = a.proxyImage
|
||||
ss.ObjectMeta = metav1.ObjectMeta{
|
||||
Name: headlessSvc.Name,
|
||||
Namespace: a.operatorNamespace,
|
||||
Labels: sts.ChildResourceLabels,
|
||||
}
|
||||
ss.Spec.ServiceName = headlessSvc.Name
|
||||
ss.Spec.Selector = &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app": sts.ParentResourceUID,
|
||||
},
|
||||
}
|
||||
mak.Set(&ss.Spec.Template.Labels, "app", sts.ParentResourceUID)
|
||||
for key, val := range sts.ChildResourceLabels {
|
||||
ss.Spec.Template.Labels[key] = val // sync StatefulSet labels to Pod to make it easier for users to select the Pod
|
||||
}
|
||||
|
||||
// Generic containerboot configuration options.
|
||||
container.Env = append(container.Env,
|
||||
corev1.EnvVar{
|
||||
Name: "TS_KUBE_SECRET",
|
||||
Value: proxySecret,
|
||||
Value: authKeySecret,
|
||||
},
|
||||
)
|
||||
if !shouldDoTailscaledDeclarativeConfig(sts) {
|
||||
container.Env = append(container.Env, corev1.EnvVar{
|
||||
corev1.EnvVar{
|
||||
Name: "TS_HOSTNAME",
|
||||
Value: sts.Hostname,
|
||||
})
|
||||
// containerboot currently doesn't have a way to re-read the hostname/ip as
|
||||
// it is passed via an environment variable. So we need to restart the
|
||||
// container when the value changes. We do this by adding an annotation to
|
||||
// the pod template that contains the last value we set.
|
||||
mak.Set(&ss.Spec.Template.Annotations, podAnnotationLastSetHostname, sts.Hostname)
|
||||
}
|
||||
// Configure containeboot to run tailscaled with a configfile read from the state Secret.
|
||||
if shouldDoTailscaledDeclarativeConfig(sts) {
|
||||
mak.Set(&ss.Spec.Template.Annotations, podAnnotationLastSetConfigFileHash, tsConfigHash)
|
||||
ss.Spec.Template.Spec.Volumes = append(ss.Spec.Template.Spec.Volumes, corev1.Volume{
|
||||
Name: "tailscaledconfig",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: proxySecret,
|
||||
Items: []corev1.KeyToPath{{
|
||||
Key: tailscaledConfigKey,
|
||||
Path: tailscaledConfigKey,
|
||||
}},
|
||||
},
|
||||
},
|
||||
})
|
||||
container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
|
||||
Name: "tailscaledconfig",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/tsconfig",
|
||||
})
|
||||
container.Env = append(container.Env, corev1.EnvVar{
|
||||
Name: "EXPERIMENTAL_TS_CONFIGFILE_PATH",
|
||||
Value: "/etc/tsconfig/tailscaled",
|
||||
})
|
||||
}
|
||||
|
||||
if a.tsFirewallMode != "" {
|
||||
container.Env = append(container.Env, corev1.EnvVar{
|
||||
Name: "TS_DEBUG_FIREWALL_MODE",
|
||||
Value: a.tsFirewallMode,
|
||||
})
|
||||
}
|
||||
ss.Spec.Template.Spec.PriorityClassName = a.proxyPriorityClassName
|
||||
|
||||
// Ingress/egress proxy configuration options.
|
||||
var configFileHash string
|
||||
if sts.ClusterTargetIP != "" {
|
||||
container.Env = append(container.Env, corev1.EnvVar{
|
||||
Name: "TS_DEST_IP",
|
||||
Value: sts.ClusterTargetIP,
|
||||
})
|
||||
mak.Set(&ss.Spec.Template.Annotations, podAnnotationLastSetClusterIP, sts.ClusterTargetIP)
|
||||
} else if sts.TailnetTargetIP != "" {
|
||||
container.Env = append(container.Env, corev1.EnvVar{
|
||||
Name: "TS_TAILNET_TARGET_IP",
|
||||
Value: sts.TailnetTargetIP,
|
||||
})
|
||||
mak.Set(&ss.Spec.Template.Annotations, podAnnotationLastSetTailnetTargetIP, sts.TailnetTargetIP)
|
||||
} else if sts.TailnetTargetFQDN != "" {
|
||||
container.Env = append(container.Env, corev1.EnvVar{
|
||||
Name: "TS_TAILNET_TARGET_FQDN",
|
||||
Value: sts.TailnetTargetFQDN,
|
||||
})
|
||||
mak.Set(&ss.Spec.Template.Annotations, podAnnotationLastSetTailnetTargetFQDN, sts.TailnetTargetFQDN)
|
||||
} else if sts.ServeConfig != nil {
|
||||
container.Env = append(container.Env, corev1.EnvVar{
|
||||
Name: "TS_SERVE_CONFIG",
|
||||
@@ -500,7 +431,7 @@ func (a *tailscaleSTSReconciler) reconcileSTS(ctx context.Context, logger *zap.S
|
||||
Name: "serve-config",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: proxySecret,
|
||||
SecretName: authKeySecret,
|
||||
Items: []corev1.KeyToPath{{
|
||||
Key: "serve-config",
|
||||
Path: "serve-config",
|
||||
@@ -508,48 +439,91 @@ func (a *tailscaleSTSReconciler) reconcileSTS(ctx context.Context, logger *zap.S
|
||||
},
|
||||
},
|
||||
})
|
||||
} else if sts.Connector != nil {
|
||||
// TODO: definitely not the right place for this
|
||||
var err error
|
||||
configFileHash, err = a.tsConfigCM(ctx, headlessSvc.Name, a.operatorNamespace, logger, sts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create configmap: %w", err)
|
||||
}
|
||||
container.Env = append(container.Env, corev1.EnvVar{
|
||||
Name: "TS_CONFIGFILE_PATH",
|
||||
Value: "/tsconfig/tailscaled",
|
||||
})
|
||||
ss.Spec.Template.Spec.Volumes = append(ss.Spec.Template.Spec.Volumes, corev1.Volume{
|
||||
Name: "configfile",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: headlessSvc.Name,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
|
||||
Name: "configfile",
|
||||
MountPath: "/tsconfig",
|
||||
})
|
||||
if sts.key != "" {
|
||||
|
||||
}
|
||||
// We need to provide these env vars even if the values are empty to
|
||||
// ensure that a transition from a Connector with a defined subnet
|
||||
// router or exit node to one without succeeds.
|
||||
// container.Env = append(container.Env, corev1.EnvVar{
|
||||
// Name: "TS_EXTRA_ARGS",
|
||||
// Value: fmt.Sprintf("--advertise-exit-node=%v", sts.Connector.isExitNode),
|
||||
// })
|
||||
// container.Env = append(container.Env, corev1.EnvVar{
|
||||
// Name: "TS_ROUTES",
|
||||
// Value: sts.Connector.routes,
|
||||
// })
|
||||
}
|
||||
if a.tsFirewallMode != "" {
|
||||
container.Env = append(container.Env, corev1.EnvVar{
|
||||
Name: "TS_DEBUG_FIREWALL_MODE",
|
||||
Value: a.tsFirewallMode,
|
||||
})
|
||||
}
|
||||
ss.ObjectMeta = metav1.ObjectMeta{
|
||||
Name: headlessSvc.Name,
|
||||
Namespace: a.operatorNamespace,
|
||||
Labels: sts.ChildResourceLabels,
|
||||
}
|
||||
ss.Spec.ServiceName = headlessSvc.Name
|
||||
ss.Spec.Selector = &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app": sts.ParentResourceUID,
|
||||
},
|
||||
}
|
||||
|
||||
// containerboot currently doesn't have a way to re-read the hostname/ip as
|
||||
// it is passed via an environment variable. So we need to restart the
|
||||
// container when the value changes. We do this by adding an annotation to
|
||||
// the pod template that contains the last value we set.
|
||||
ss.Spec.Template.Annotations = map[string]string{
|
||||
podAnnotationLastSetHostname: sts.Hostname,
|
||||
}
|
||||
if sts.ClusterTargetIP != "" {
|
||||
ss.Spec.Template.Annotations[podAnnotationLastSetClusterIP] = sts.ClusterTargetIP
|
||||
}
|
||||
if sts.TailnetTargetIP != "" {
|
||||
ss.Spec.Template.Annotations[podAnnotationLastSetTailnetTargetIP] = sts.TailnetTargetIP
|
||||
}
|
||||
if sts.TailnetTargetFQDN != "" {
|
||||
ss.Spec.Template.Annotations[podAnnotationLastSetTailnetTargetFQDN] = sts.TailnetTargetFQDN
|
||||
}
|
||||
if configFileHash != "" {
|
||||
ss.Spec.Template.Annotations[podAnnotationLastSetTailnetTargetFQDN] = configFileHash
|
||||
}
|
||||
ss.Spec.Template.Labels = map[string]string{
|
||||
"app": sts.ParentResourceUID,
|
||||
}
|
||||
ss.Spec.Template.Spec.PriorityClassName = a.proxyPriorityClassName
|
||||
logger.Debugf("reconciling statefulset %s/%s", ss.GetNamespace(), ss.GetName())
|
||||
return createOrUpdate(ctx, a.Client, a.operatorNamespace, &ss, func(s *appsv1.StatefulSet) { s.Spec = ss.Spec })
|
||||
}
|
||||
|
||||
// tailscaledConfig takes a proxy config, a newly generated auth key if
|
||||
// generated and a Secret with the previous proxy state and auth key and
|
||||
// produces returns tailscaled configuration and a hash of that configuration.
|
||||
func tailscaledConfig(stsC *tailscaleSTSConfig, newAuthkey string, oldSecret *corev1.Secret) ([]byte, string, error) {
|
||||
conf := ipn.ConfigVAlpha{
|
||||
Version: "alpha0",
|
||||
AcceptDNS: "false",
|
||||
Locked: "false",
|
||||
Hostname: &stsC.Hostname,
|
||||
}
|
||||
if stsC.Connector != nil {
|
||||
routes, err := netutil.CalcAdvertiseRoutes(stsC.Connector.routes, stsC.Connector.isExitNode)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error calculating routes: %w", err)
|
||||
}
|
||||
conf.AdvertiseRoutes = routes
|
||||
}
|
||||
if newAuthkey != "" {
|
||||
conf.AuthKey = &newAuthkey
|
||||
} else if oldSecret != nil && len(oldSecret.Data[tailscaledConfigKey]) > 0 { // write to StringData, read from Data as StringData is write-only
|
||||
origConf := &ipn.ConfigVAlpha{}
|
||||
if err := json.Unmarshal([]byte(oldSecret.Data[tailscaledConfigKey]), origConf); err != nil {
|
||||
return nil, "", fmt.Errorf("error unmarshaling previous tailscaled config: %w", err)
|
||||
}
|
||||
conf.AuthKey = origConf.AuthKey
|
||||
}
|
||||
confFileBytes, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error marshaling tailscaled config : %w", err)
|
||||
}
|
||||
hash, err := hashBytes(confFileBytes)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error calculating config hash: %w", err)
|
||||
}
|
||||
return confFileBytes, hash, nil
|
||||
}
|
||||
|
||||
// ptrObject is a type constraint for pointer types that implement
|
||||
// client.Object.
|
||||
type ptrObject[T any] interface {
|
||||
@@ -557,24 +531,6 @@ type ptrObject[T any] interface {
|
||||
*T
|
||||
}
|
||||
|
||||
// hashBytes produces a hash for the provided bytes that is the same across
|
||||
// different invocations of this code. We do not use the
|
||||
// tailscale.com/deephash.Hash here because that produces a different hash for
|
||||
// the same value in different tailscale builds. The hash we are producing here
|
||||
// is used to determine if the container running the Connector Tailscale node
|
||||
// needs to be restarted. The container does not need restarting when the only
|
||||
// thing that changed is operator version (the hash is also exposed to users via
|
||||
// an annotation and might be confusing if it changes without the config having
|
||||
// changed).
|
||||
func hashBytes(b []byte) (string, error) {
|
||||
h := sha256.New()
|
||||
_, err := h.Write(b)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error calculating hash: %w", err)
|
||||
}
|
||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// createOrUpdate adds obj to the k8s cluster, unless the object already exists,
|
||||
// in which case update is called to make changes to it. If update is nil, the
|
||||
// existing object is returned unmodified.
|
||||
@@ -678,10 +634,3 @@ func nameForService(svc *corev1.Service) (string, error) {
|
||||
func isValidFirewallMode(m string) bool {
|
||||
return m == "auto" || m == "nftables" || m == "iptables"
|
||||
}
|
||||
|
||||
// shouldDoTailscaledDeclarativeConfig determines whether the proxy instance
|
||||
// should be configured to run tailscaled only with a all config opts passed to
|
||||
// tailscaled.
|
||||
func shouldDoTailscaledDeclarativeConfig(stsC *tailscaleSTSConfig) bool {
|
||||
return stsC.Connector != nil
|
||||
}
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -22,20 +19,32 @@ import (
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.28.4/staging/src/k8s.io/apiserver/pkg/storage/names/generate.go#L45.
|
||||
// https://github.com/kubernetes/kubernetes/pull/116430
|
||||
func Test_statefulSetNameBase(t *testing.T) {
|
||||
// Service name lengths can be 1 - 63 chars, be paranoid and test them all.
|
||||
var b strings.Builder
|
||||
for b.Len() < 63 {
|
||||
if _, err := b.WriteString("a"); err != nil {
|
||||
t.Fatalf("error writing to string builder: %v", err)
|
||||
}
|
||||
baseLength := b.Len()
|
||||
if baseLength > 43 {
|
||||
baseLength = 43 // currently 43 is the max base length
|
||||
}
|
||||
wantsNameR := regexp.MustCompile(`^ts-a{` + fmt.Sprint(baseLength) + `}-$`) // to match a string like ts-aaaa-
|
||||
gotName := statefulSetNameBase(b.String())
|
||||
if !wantsNameR.MatchString(gotName) {
|
||||
t.Fatalf("expected string %s to match regex %s ", gotName, wantsNameR.String()) // fatal rather than error as this test is called 63 times
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{
|
||||
name: "43 chars",
|
||||
in: "oidhexl9o832hcbhyg4uz6o0s7u9uae54h5k8ofs9xb",
|
||||
out: "ts-oidhexl9o832hcbhyg4uz6o0s7u9uae54h5k8ofs9xb-",
|
||||
},
|
||||
{
|
||||
name: "44 chars",
|
||||
in: "oidhexl9o832hcbhyg4uz6o0s7u9uae54h5k8ofs9xbo",
|
||||
out: "ts-oidhexl9o832hcbhyg4uz6o0s7u9uae54h5k8ofs9xb-",
|
||||
},
|
||||
{
|
||||
name: "42 chars",
|
||||
in: "oidhexl9o832hcbhyg4uz6o0s7u9uae54h5k8ofs9x",
|
||||
out: "ts-oidhexl9o832hcbhyg4uz6o0s7u9uae54h5k8ofs9x-",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := statefulSetNameBase(tt.in); got != tt.out {
|
||||
t.Errorf("stsNamePrefix(%s) = %q, want %s", tt.in, got, tt.out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,417 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !plan9
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/types/ptr"
|
||||
"tailscale.com/util/mak"
|
||||
)
|
||||
|
||||
// confgOpts contains configuration options for creating cluster resources for
|
||||
// Tailscale proxies.
|
||||
type configOpts struct {
|
||||
stsName string
|
||||
secretName string
|
||||
hostname string
|
||||
namespace string
|
||||
parentType string
|
||||
priorityClassName string
|
||||
firewallMode string
|
||||
tailnetTargetIP string
|
||||
tailnetTargetFQDN string
|
||||
clusterTargetIP string
|
||||
subnetRoutes string
|
||||
isExitNode bool
|
||||
shouldUseDeclarativeConfig bool // tailscaled in proxy should be configured using config file
|
||||
confFileHash string
|
||||
}
|
||||
|
||||
func expectedSTS(opts configOpts) *appsv1.StatefulSet {
|
||||
tsContainer := corev1.Container{
|
||||
Name: "tailscale",
|
||||
Image: "tailscale/tailscale",
|
||||
Env: []corev1.EnvVar{
|
||||
{Name: "TS_USERSPACE", Value: "false"},
|
||||
{Name: "TS_AUTH_ONCE", Value: "true"},
|
||||
{Name: "TS_KUBE_SECRET", Value: opts.secretName},
|
||||
},
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"NET_ADMIN"},
|
||||
},
|
||||
},
|
||||
ImagePullPolicy: "Always",
|
||||
}
|
||||
annots := make(map[string]string)
|
||||
var volumes []corev1.Volume
|
||||
if opts.shouldUseDeclarativeConfig {
|
||||
volumes = []corev1.Volume{
|
||||
{
|
||||
Name: "tailscaledconfig",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: opts.secretName,
|
||||
Items: []corev1.KeyToPath{
|
||||
{
|
||||
Key: "tailscaled",
|
||||
Path: "tailscaled",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
tsContainer.VolumeMounts = []corev1.VolumeMount{{
|
||||
Name: "tailscaledconfig",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/tsconfig",
|
||||
}}
|
||||
tsContainer.Env = append(tsContainer.Env, corev1.EnvVar{
|
||||
Name: "EXPERIMENTAL_TS_CONFIGFILE_PATH",
|
||||
Value: "/etc/tsconfig/tailscaled",
|
||||
})
|
||||
annots["tailscale.com/operator-last-set-config-file-hash"] = opts.confFileHash
|
||||
} else {
|
||||
tsContainer.Env = append(tsContainer.Env, corev1.EnvVar{Name: "TS_HOSTNAME", Value: opts.hostname})
|
||||
annots["tailscale.com/operator-last-set-hostname"] = opts.hostname
|
||||
}
|
||||
if opts.firewallMode != "" {
|
||||
tsContainer.Env = append(tsContainer.Env, corev1.EnvVar{
|
||||
Name: "TS_DEBUG_FIREWALL_MODE",
|
||||
Value: opts.firewallMode,
|
||||
})
|
||||
}
|
||||
if opts.tailnetTargetIP != "" {
|
||||
annots["tailscale.com/operator-last-set-ts-tailnet-target-ip"] = opts.tailnetTargetIP
|
||||
tsContainer.Env = append(tsContainer.Env, corev1.EnvVar{
|
||||
Name: "TS_TAILNET_TARGET_IP",
|
||||
Value: opts.tailnetTargetIP,
|
||||
})
|
||||
} else if opts.tailnetTargetFQDN != "" {
|
||||
annots["tailscale.com/operator-last-set-ts-tailnet-target-fqdn"] = opts.tailnetTargetFQDN
|
||||
tsContainer.Env = append(tsContainer.Env, corev1.EnvVar{
|
||||
Name: "TS_TAILNET_TARGET_FQDN",
|
||||
Value: opts.tailnetTargetFQDN,
|
||||
})
|
||||
|
||||
} else if opts.clusterTargetIP != "" {
|
||||
tsContainer.Env = append(tsContainer.Env, corev1.EnvVar{
|
||||
Name: "TS_DEST_IP",
|
||||
Value: opts.clusterTargetIP,
|
||||
})
|
||||
annots["tailscale.com/operator-last-set-cluster-ip"] = opts.clusterTargetIP
|
||||
}
|
||||
return &appsv1.StatefulSet{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "StatefulSet",
|
||||
APIVersion: "apps/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: opts.stsName,
|
||||
Namespace: "operator-ns",
|
||||
Labels: map[string]string{
|
||||
"tailscale.com/managed": "true",
|
||||
"tailscale.com/parent-resource": "test",
|
||||
"tailscale.com/parent-resource-ns": opts.namespace,
|
||||
"tailscale.com/parent-resource-type": opts.parentType,
|
||||
},
|
||||
},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
Replicas: ptr.To[int32](1),
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"app": "1234-UID"},
|
||||
},
|
||||
ServiceName: opts.stsName,
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: annots,
|
||||
DeletionGracePeriodSeconds: ptr.To[int64](10),
|
||||
Labels: map[string]string{
|
||||
"tailscale.com/managed": "true",
|
||||
"tailscale.com/parent-resource": "test",
|
||||
"tailscale.com/parent-resource-ns": opts.namespace,
|
||||
"tailscale.com/parent-resource-type": opts.parentType,
|
||||
"app": "1234-UID",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
ServiceAccountName: "proxies",
|
||||
PriorityClassName: opts.priorityClassName,
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "sysctler",
|
||||
Image: "tailscale/tailscale",
|
||||
Command: []string{"/bin/sh"},
|
||||
Args: []string{"-c", "sysctl -w net.ipv4.ip_forward=1 net.ipv6.conf.all.forwarding=1"},
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []corev1.Container{tsContainer},
|
||||
Volumes: volumes,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func expectedHeadlessService(name string) *corev1.Service {
|
||||
return &corev1.Service{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Service",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
GenerateName: "ts-test-",
|
||||
Namespace: "operator-ns",
|
||||
Labels: map[string]string{
|
||||
"tailscale.com/managed": "true",
|
||||
"tailscale.com/parent-resource": "test",
|
||||
"tailscale.com/parent-resource-ns": "default",
|
||||
"tailscale.com/parent-resource-type": "svc",
|
||||
},
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Selector: map[string]string{
|
||||
"app": "1234-UID",
|
||||
},
|
||||
ClusterIP: "None",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func expectedSecret(t *testing.T, opts configOpts) *corev1.Secret {
|
||||
t.Helper()
|
||||
labels := map[string]string{
|
||||
"tailscale.com/managed": "true",
|
||||
"tailscale.com/parent-resource": "test",
|
||||
"tailscale.com/parent-resource-type": opts.parentType,
|
||||
}
|
||||
s := &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: opts.secretName,
|
||||
Namespace: "operator-ns",
|
||||
},
|
||||
}
|
||||
if !opts.shouldUseDeclarativeConfig {
|
||||
mak.Set(&s.StringData, "authkey", "secret-authkey")
|
||||
labels["tailscale.com/parent-resource-ns"] = opts.namespace
|
||||
} else {
|
||||
conf := &ipn.ConfigVAlpha{
|
||||
Version: "alpha0",
|
||||
AcceptDNS: "false",
|
||||
Hostname: &opts.hostname,
|
||||
Locked: "false",
|
||||
AuthKey: ptr.To("secret-authkey"),
|
||||
}
|
||||
var routes []netip.Prefix
|
||||
if opts.subnetRoutes != "" || opts.isExitNode {
|
||||
r := opts.subnetRoutes
|
||||
if opts.isExitNode {
|
||||
r = "0.0.0.0/0,::/0," + r
|
||||
}
|
||||
for _, rr := range strings.Split(r, ",") {
|
||||
prefix, err := netip.ParsePrefix(rr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
routes = append(routes, prefix)
|
||||
}
|
||||
}
|
||||
conf.AdvertiseRoutes = routes
|
||||
b, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
t.Fatalf("error marshalling tailscaled config")
|
||||
}
|
||||
mak.Set(&s.StringData, "tailscaled", string(b))
|
||||
labels["tailscale.com/parent-resource-ns"] = "" // Connector is cluster scoped
|
||||
}
|
||||
s.Labels = labels
|
||||
return s
|
||||
}
|
||||
|
||||
func findGenName(t *testing.T, client client.Client, ns, name, typ string) (full, noSuffix string) {
|
||||
t.Helper()
|
||||
labels := map[string]string{
|
||||
LabelManaged: "true",
|
||||
LabelParentName: name,
|
||||
LabelParentNamespace: ns,
|
||||
LabelParentType: typ,
|
||||
}
|
||||
s, err := getSingleObject[corev1.Secret](context.Background(), client, "operator-ns", labels)
|
||||
if err != nil {
|
||||
t.Fatalf("finding secret for %q: %v", name, err)
|
||||
}
|
||||
if s == nil {
|
||||
t.Fatalf("no secret found for %q %s %+#v", name, ns, labels)
|
||||
}
|
||||
return s.GetName(), strings.TrimSuffix(s.GetName(), "-0")
|
||||
}
|
||||
|
||||
func mustCreate(t *testing.T, client client.Client, obj client.Object) {
|
||||
t.Helper()
|
||||
if err := client.Create(context.Background(), obj); err != nil {
|
||||
t.Fatalf("creating %q: %v", obj.GetName(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func mustUpdate[T any, O ptrObject[T]](t *testing.T, client client.Client, ns, name string, update func(O)) {
|
||||
t.Helper()
|
||||
obj := O(new(T))
|
||||
if err := client.Get(context.Background(), types.NamespacedName{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
}, obj); err != nil {
|
||||
t.Fatalf("getting %q: %v", name, err)
|
||||
}
|
||||
update(obj)
|
||||
if err := client.Update(context.Background(), obj); err != nil {
|
||||
t.Fatalf("updating %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func mustUpdateStatus[T any, O ptrObject[T]](t *testing.T, client client.Client, ns, name string, update func(O)) {
|
||||
t.Helper()
|
||||
obj := O(new(T))
|
||||
if err := client.Get(context.Background(), types.NamespacedName{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
}, obj); err != nil {
|
||||
t.Fatalf("getting %q: %v", name, err)
|
||||
}
|
||||
update(obj)
|
||||
if err := client.Status().Update(context.Background(), obj); err != nil {
|
||||
t.Fatalf("updating %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func expectEqual[T any, O ptrObject[T]](t *testing.T, client client.Client, want O) {
|
||||
t.Helper()
|
||||
got := O(new(T))
|
||||
if err := client.Get(context.Background(), types.NamespacedName{
|
||||
Name: want.GetName(),
|
||||
Namespace: want.GetNamespace(),
|
||||
}, got); err != nil {
|
||||
t.Fatalf("getting %q: %v", want.GetName(), err)
|
||||
}
|
||||
// The resource version changes eagerly whenever the operator does even a
|
||||
// no-op update. Asserting a specific value leads to overly brittle tests,
|
||||
// so just remove it from both got and want.
|
||||
got.SetResourceVersion("")
|
||||
want.SetResourceVersion("")
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Fatalf("unexpected object (-got +want):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func expectMissing[T any, O ptrObject[T]](t *testing.T, client client.Client, ns, name string) {
|
||||
t.Helper()
|
||||
obj := O(new(T))
|
||||
if err := client.Get(context.Background(), types.NamespacedName{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
}, obj); !apierrors.IsNotFound(err) {
|
||||
t.Fatalf("object %s/%s unexpectedly present, wanted missing", ns, name)
|
||||
}
|
||||
}
|
||||
|
||||
func expectReconciled(t *testing.T, sr reconcile.Reconciler, ns, name string) {
|
||||
t.Helper()
|
||||
req := reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: ns,
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
res, err := sr.Reconcile(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Reconcile: unexpected error: %v", err)
|
||||
}
|
||||
if res.Requeue {
|
||||
t.Fatalf("unexpected immediate requeue")
|
||||
}
|
||||
if res.RequeueAfter != 0 {
|
||||
t.Fatalf("unexpected timed requeue (%v)", res.RequeueAfter)
|
||||
}
|
||||
}
|
||||
|
||||
func expectRequeue(t *testing.T, sr reconcile.Reconciler, ns, name string) {
|
||||
t.Helper()
|
||||
req := reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
},
|
||||
}
|
||||
res, err := sr.Reconcile(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Reconcile: unexpected error: %v", err)
|
||||
}
|
||||
if res.RequeueAfter == 0 {
|
||||
t.Fatalf("expected timed requeue, got success")
|
||||
}
|
||||
}
|
||||
|
||||
type fakeTSClient struct {
|
||||
sync.Mutex
|
||||
keyRequests []tailscale.KeyCapabilities
|
||||
deleted []string
|
||||
}
|
||||
|
||||
func (c *fakeTSClient) CreateKey(ctx context.Context, caps tailscale.KeyCapabilities) (string, *tailscale.Key, error) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.keyRequests = append(c.keyRequests, caps)
|
||||
k := &tailscale.Key{
|
||||
ID: "key",
|
||||
Created: time.Now(),
|
||||
Capabilities: caps,
|
||||
}
|
||||
return "secret-authkey", k, nil
|
||||
}
|
||||
|
||||
func (c *fakeTSClient) DeleteDevice(ctx context.Context, deviceID string) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.deleted = append(c.deleted, deviceID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeTSClient) KeyRequests() []tailscale.KeyCapabilities {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
return c.keyRequests
|
||||
}
|
||||
|
||||
func (c *fakeTSClient) Deleted() []string {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
return c.deleted
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depaware)
|
||||
|
||||
github.com/beorn7/perks/quantile from github.com/prometheus/client_golang/prometheus
|
||||
💣 github.com/cespare/xxhash/v2 from github.com/prometheus/client_golang/prometheus
|
||||
github.com/google/uuid from tailscale.com/tsweb
|
||||
💣 github.com/prometheus/client_golang/prometheus from tailscale.com/tsweb/promvarz
|
||||
github.com/prometheus/client_golang/prometheus/internal from github.com/prometheus/client_golang/prometheus
|
||||
github.com/prometheus/client_model/go from github.com/prometheus/client_golang/prometheus+
|
||||
github.com/prometheus/common/expfmt from github.com/prometheus/client_golang/prometheus+
|
||||
github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg from github.com/prometheus/common/expfmt
|
||||
github.com/prometheus/common/model from github.com/prometheus/client_golang/prometheus+
|
||||
LD github.com/prometheus/procfs from github.com/prometheus/client_golang/prometheus
|
||||
LD github.com/prometheus/procfs/internal/fs from github.com/prometheus/procfs
|
||||
LD github.com/prometheus/procfs/internal/util from github.com/prometheus/procfs
|
||||
💣 go4.org/mem from tailscale.com/metrics+
|
||||
go4.org/netipx from tailscale.com/net/tsaddr
|
||||
google.golang.org/protobuf/encoding/protodelim from github.com/prometheus/common/expfmt
|
||||
google.golang.org/protobuf/encoding/prototext from github.com/prometheus/common/expfmt+
|
||||
google.golang.org/protobuf/encoding/protowire from google.golang.org/protobuf/encoding/protodelim+
|
||||
google.golang.org/protobuf/internal/descfmt from google.golang.org/protobuf/internal/filedesc
|
||||
google.golang.org/protobuf/internal/descopts from google.golang.org/protobuf/internal/filedesc+
|
||||
google.golang.org/protobuf/internal/detrand from google.golang.org/protobuf/internal/descfmt+
|
||||
google.golang.org/protobuf/internal/encoding/defval from google.golang.org/protobuf/internal/encoding/tag+
|
||||
google.golang.org/protobuf/internal/encoding/messageset from google.golang.org/protobuf/encoding/prototext+
|
||||
google.golang.org/protobuf/internal/encoding/tag from google.golang.org/protobuf/internal/impl
|
||||
google.golang.org/protobuf/internal/encoding/text from google.golang.org/protobuf/encoding/prototext+
|
||||
google.golang.org/protobuf/internal/errors from google.golang.org/protobuf/encoding/prototext+
|
||||
google.golang.org/protobuf/internal/filedesc from google.golang.org/protobuf/internal/encoding/tag+
|
||||
google.golang.org/protobuf/internal/filetype from google.golang.org/protobuf/runtime/protoimpl
|
||||
google.golang.org/protobuf/internal/flags from google.golang.org/protobuf/encoding/prototext+
|
||||
google.golang.org/protobuf/internal/genid from google.golang.org/protobuf/encoding/prototext+
|
||||
💣 google.golang.org/protobuf/internal/impl from google.golang.org/protobuf/internal/filetype+
|
||||
google.golang.org/protobuf/internal/order from google.golang.org/protobuf/encoding/prototext+
|
||||
google.golang.org/protobuf/internal/pragma from google.golang.org/protobuf/encoding/prototext+
|
||||
google.golang.org/protobuf/internal/set from google.golang.org/protobuf/encoding/prototext
|
||||
💣 google.golang.org/protobuf/internal/strs from google.golang.org/protobuf/encoding/prototext+
|
||||
google.golang.org/protobuf/internal/version from google.golang.org/protobuf/runtime/protoimpl
|
||||
google.golang.org/protobuf/proto from github.com/prometheus/client_golang/prometheus+
|
||||
💣 google.golang.org/protobuf/reflect/protoreflect from github.com/prometheus/client_model/go+
|
||||
google.golang.org/protobuf/reflect/protoregistry from google.golang.org/protobuf/encoding/prototext+
|
||||
google.golang.org/protobuf/runtime/protoiface from google.golang.org/protobuf/internal/impl+
|
||||
google.golang.org/protobuf/runtime/protoimpl from github.com/prometheus/client_model/go+
|
||||
google.golang.org/protobuf/types/known/timestamppb from github.com/prometheus/client_golang/prometheus+
|
||||
tailscale.com from tailscale.com/version
|
||||
tailscale.com/envknob from tailscale.com/tsweb+
|
||||
tailscale.com/metrics from tailscale.com/net/stunserver+
|
||||
tailscale.com/net/netaddr from tailscale.com/net/tsaddr
|
||||
tailscale.com/net/stun from tailscale.com/net/stunserver
|
||||
tailscale.com/net/stunserver from tailscale.com/cmd/stund
|
||||
tailscale.com/net/tsaddr from tailscale.com/tsweb
|
||||
tailscale.com/tailcfg from tailscale.com/version
|
||||
tailscale.com/tsweb from tailscale.com/cmd/stund
|
||||
tailscale.com/tsweb/promvarz from tailscale.com/tsweb
|
||||
tailscale.com/tsweb/varz from tailscale.com/tsweb+
|
||||
tailscale.com/types/dnstype from tailscale.com/tailcfg
|
||||
tailscale.com/types/ipproto from tailscale.com/tailcfg
|
||||
tailscale.com/types/key from tailscale.com/tailcfg
|
||||
tailscale.com/types/lazy from tailscale.com/version+
|
||||
tailscale.com/types/logger from tailscale.com/tsweb
|
||||
tailscale.com/types/opt from tailscale.com/envknob+
|
||||
tailscale.com/types/ptr from tailscale.com/tailcfg
|
||||
tailscale.com/types/structs from tailscale.com/tailcfg+
|
||||
tailscale.com/types/tkatype from tailscale.com/tailcfg+
|
||||
tailscale.com/types/views from tailscale.com/net/tsaddr+
|
||||
tailscale.com/util/cmpx from tailscale.com/tailcfg+
|
||||
tailscale.com/util/ctxkey from tailscale.com/tsweb+
|
||||
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
|
||||
tailscale.com/util/dnsname from tailscale.com/tailcfg
|
||||
tailscale.com/util/lineread from tailscale.com/version/distro
|
||||
tailscale.com/util/nocasemaps from tailscale.com/types/ipproto
|
||||
tailscale.com/util/slicesx from tailscale.com/tailcfg
|
||||
tailscale.com/util/vizerror from tailscale.com/tailcfg+
|
||||
tailscale.com/version from tailscale.com/envknob+
|
||||
tailscale.com/version/distro from tailscale.com/envknob
|
||||
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
|
||||
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
|
||||
golang.org/x/crypto/chacha20poly1305 from crypto/tls
|
||||
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
|
||||
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
|
||||
golang.org/x/crypto/curve25519 from golang.org/x/crypto/nacl/box+
|
||||
golang.org/x/crypto/hkdf from crypto/tls
|
||||
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||
golang.org/x/net/dns/dnsmessage from net
|
||||
golang.org/x/net/http/httpguts from net/http
|
||||
golang.org/x/net/http/httpproxy from net/http
|
||||
golang.org/x/net/http2/hpack from net/http
|
||||
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
||||
D golang.org/x/net/route from net
|
||||
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
|
||||
LD golang.org/x/sys/unix from github.com/prometheus/procfs+
|
||||
W golang.org/x/sys/windows from github.com/prometheus/client_golang/prometheus
|
||||
golang.org/x/text/secure/bidirule from golang.org/x/net/idna
|
||||
golang.org/x/text/transform from golang.org/x/text/secure/bidirule+
|
||||
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
|
||||
golang.org/x/text/unicode/norm from golang.org/x/net/idna
|
||||
bufio from compress/flate+
|
||||
bytes from bufio+
|
||||
cmp from slices
|
||||
compress/flate from compress/gzip
|
||||
compress/gzip from google.golang.org/protobuf/internal/impl+
|
||||
container/list from crypto/tls+
|
||||
context from crypto/tls+
|
||||
crypto from crypto/ecdh+
|
||||
crypto/aes from crypto/ecdsa+
|
||||
crypto/cipher from crypto/aes+
|
||||
crypto/des from crypto/tls+
|
||||
crypto/dsa from crypto/x509
|
||||
crypto/ecdh from crypto/ecdsa+
|
||||
crypto/ecdsa from crypto/tls+
|
||||
crypto/ed25519 from crypto/tls+
|
||||
crypto/elliptic from crypto/ecdsa+
|
||||
crypto/hmac from crypto/tls+
|
||||
crypto/md5 from crypto/tls+
|
||||
crypto/rand from crypto/ed25519+
|
||||
crypto/rc4 from crypto/tls
|
||||
crypto/rsa from crypto/tls+
|
||||
crypto/sha1 from crypto/tls+
|
||||
crypto/sha256 from crypto/tls+
|
||||
crypto/sha512 from crypto/ecdsa+
|
||||
crypto/subtle from crypto/aes+
|
||||
crypto/tls from net/http+
|
||||
crypto/x509 from crypto/tls
|
||||
crypto/x509/pkix from crypto/x509
|
||||
database/sql/driver from github.com/google/uuid
|
||||
embed from crypto/internal/nistec+
|
||||
encoding from encoding/json+
|
||||
encoding/asn1 from crypto/x509+
|
||||
encoding/base64 from encoding/json+
|
||||
encoding/binary from compress/gzip+
|
||||
encoding/hex from crypto/x509+
|
||||
encoding/json from expvar+
|
||||
encoding/pem from crypto/tls+
|
||||
errors from bufio+
|
||||
expvar from github.com/prometheus/client_golang/prometheus+
|
||||
flag from tailscale.com/cmd/stund
|
||||
fmt from compress/flate+
|
||||
go/token from google.golang.org/protobuf/internal/strs
|
||||
hash from crypto+
|
||||
hash/crc32 from compress/gzip+
|
||||
hash/fnv from google.golang.org/protobuf/internal/detrand
|
||||
hash/maphash from go4.org/mem
|
||||
html from net/http/pprof+
|
||||
io from bufio+
|
||||
io/fs from crypto/x509+
|
||||
io/ioutil from golang.org/x/sys/cpu+
|
||||
log from expvar+
|
||||
log/internal from log
|
||||
maps from tailscale.com/tailcfg+
|
||||
math from compress/flate+
|
||||
math/big from crypto/dsa+
|
||||
math/bits from compress/flate+
|
||||
math/rand from math/big+
|
||||
mime from github.com/prometheus/common/expfmt+
|
||||
mime/multipart from net/http
|
||||
mime/quotedprintable from mime/multipart
|
||||
net from crypto/tls+
|
||||
net/http from expvar+
|
||||
net/http/httptrace from net/http
|
||||
net/http/internal from net/http
|
||||
net/http/pprof from tailscale.com/tsweb+
|
||||
net/netip from go4.org/netipx+
|
||||
net/textproto from golang.org/x/net/http/httpguts+
|
||||
net/url from crypto/x509+
|
||||
os from crypto/rand+
|
||||
os/signal from tailscale.com/cmd/stund
|
||||
path from github.com/prometheus/client_golang/prometheus/internal+
|
||||
path/filepath from crypto/x509+
|
||||
reflect from crypto/x509+
|
||||
regexp from github.com/prometheus/client_golang/prometheus/internal+
|
||||
regexp/syntax from regexp
|
||||
runtime/debug from github.com/prometheus/client_golang/prometheus+
|
||||
runtime/metrics from github.com/prometheus/client_golang/prometheus+
|
||||
runtime/pprof from net/http/pprof
|
||||
runtime/trace from net/http/pprof
|
||||
slices from tailscale.com/metrics+
|
||||
sort from compress/flate+
|
||||
strconv from compress/flate+
|
||||
strings from bufio+
|
||||
sync from compress/flate+
|
||||
sync/atomic from context+
|
||||
syscall from crypto/rand+
|
||||
text/tabwriter from runtime/pprof
|
||||
time from compress/gzip+
|
||||
unicode from bytes+
|
||||
unicode/utf16 from crypto/x509+
|
||||
unicode/utf8 from bufio+
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// The stund binary is a standalone STUN server.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"tailscale.com/net/stunserver"
|
||||
"tailscale.com/tsweb"
|
||||
)
|
||||
|
||||
var (
|
||||
stunAddr = flag.String("stun", ":3478", "UDP address on which to start the STUN server")
|
||||
httpAddr = flag.String("http", ":3479", "address on which to start the debug http server")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer cancel()
|
||||
|
||||
log.Printf("HTTP server listening on %s", *httpAddr)
|
||||
go http.ListenAndServe(*httpAddr, mux())
|
||||
|
||||
s := stunserver.New(ctx)
|
||||
if err := s.ListenAndServe(*stunAddr); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func mux() *http.ServeMux {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
io.WriteString(w, "<h1>stund</h1><a href=/debug>/debug</a>")
|
||||
})
|
||||
debug := tsweb.Debugger(mux)
|
||||
debug.KV("stun_addr", *stunAddr)
|
||||
return mux
|
||||
}
|
||||
@@ -718,24 +718,6 @@ func TestPrefsFromUpArgs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "via_route_good_16_bit",
|
||||
goos: "linux",
|
||||
args: upArgsT{
|
||||
advertiseRoutes: "fd7a:115c:a1e0:b1a::aabb:10.0.0.0/112",
|
||||
netfilterMode: "off",
|
||||
},
|
||||
want: &ipn.Prefs{
|
||||
WantRunning: true,
|
||||
NoSNAT: true,
|
||||
AdvertiseRoutes: []netip.Prefix{
|
||||
netip.MustParsePrefix("fd7a:115c:a1e0:b1a::aabb:10.0.0.0/112"),
|
||||
},
|
||||
AutoUpdate: ipn.AutoUpdatePrefs{
|
||||
Check: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "via_route_short_prefix",
|
||||
goos: "linux",
|
||||
@@ -752,7 +734,7 @@ func TestPrefsFromUpArgs(t *testing.T) {
|
||||
advertiseRoutes: "fd7a:115c:a1e0:b1a:1234:5678::/112",
|
||||
netfilterMode: "off",
|
||||
},
|
||||
wantErr: "route fd7a:115c:a1e0:b1a:1234:5678::/112 contains invalid site ID 12345678; must be 0xffff or less",
|
||||
wantErr: "route fd7a:115c:a1e0:b1a:1234:5678::/112 contains invalid site ID 12345678; must be 0xff or less",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -274,16 +274,6 @@ var debugCmd = &ffcli.Command{
|
||||
Exec: runPeerEndpointChanges,
|
||||
ShortHelp: "prints debug information about a peer's endpoint changes",
|
||||
},
|
||||
{
|
||||
Name: "dial-types",
|
||||
Exec: runDebugDialTypes,
|
||||
ShortHelp: "prints debug information about connecting to a given host or IP",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("dial-types")
|
||||
fs.StringVar(&debugDialTypesArgs.network, "network", "tcp", `network type to dial ("tcp", "udp", etc.)`)
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -693,8 +683,8 @@ func runVia(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid site-id %q; must be decimal or hex with 0x prefix", args[0])
|
||||
}
|
||||
if siteID > 0xffff {
|
||||
return fmt.Errorf("site-id values over 65535 are currently reserved")
|
||||
if siteID > 0xff {
|
||||
return fmt.Errorf("site-id values over 255 are currently reserved")
|
||||
}
|
||||
ipp, err := netip.ParsePrefix(args[1])
|
||||
if err != nil {
|
||||
@@ -1025,61 +1015,3 @@ func debugControlKnobs(ctx context.Context, args []string) error {
|
||||
e.Encode(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
var debugDialTypesArgs struct {
|
||||
network string
|
||||
}
|
||||
|
||||
func runDebugDialTypes(ctx context.Context, args []string) error {
|
||||
st, err := localClient.Status(ctx)
|
||||
if err != nil {
|
||||
return fixTailscaledConnectError(err)
|
||||
}
|
||||
description, ok := isRunningOrStarting(st)
|
||||
if !ok {
|
||||
printf("%s\n", description)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(args) != 2 || args[0] == "" || args[1] == "" {
|
||||
return errors.New("usage: dial-types <hostname-or-IP> <port>")
|
||||
}
|
||||
|
||||
port, err := strconv.ParseUint(args[1], 10, 16)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid port %q: %w", args[1], err)
|
||||
}
|
||||
|
||||
hostOrIP := args[0]
|
||||
ip, _, err := tailscaleIPFromArg(ctx, hostOrIP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ip != hostOrIP {
|
||||
log.Printf("lookup %q => %q", hostOrIP, ip)
|
||||
}
|
||||
|
||||
qparams := make(url.Values)
|
||||
qparams.Set("ip", ip)
|
||||
qparams.Set("port", strconv.FormatUint(port, 10))
|
||||
qparams.Set("network", debugDialTypesArgs.network)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", "http://local-tailscaled.sock/localapi/v0/debug-dial-types?"+qparams.Encode(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := localClient.DoLocalRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s", body)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@ import (
|
||||
var exitNodeCmd = &ffcli.Command{
|
||||
Name: "exit-node",
|
||||
ShortUsage: "exit-node [flags]",
|
||||
ShortHelp: "Show machines on your tailnet configured as exit nodes",
|
||||
LongHelp: "Show machines on your tailnet configured as exit nodes",
|
||||
Subcommands: []*ffcli.Command{
|
||||
{
|
||||
Name: "list",
|
||||
|
||||
@@ -272,7 +272,7 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc {
|
||||
// if foreground mode, create a WatchIPNBus session
|
||||
// and use the nested config for all following operations
|
||||
// TODO(marwan-at-work): nested-config validations should happen here or previous to this point.
|
||||
watcher, err = e.lc.WatchIPNBus(ctx, ipn.NotifyInitialState|ipn.NotifyNoPrivateKeys)
|
||||
watcher, err = e.lc.WatchIPNBus(ctx, ipn.NotifyInitialState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
"tailscale.com/client/web"
|
||||
"tailscale.com/clientupdate"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/net/netutil"
|
||||
@@ -194,15 +193,7 @@ func runSet(ctx context.Context, args []string) (retErr error) {
|
||||
}
|
||||
|
||||
_, err = localClient.EditPrefs(ctx, maskedPrefs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if setArgs.runWebClient && len(st.TailscaleIPs) > 0 {
|
||||
printf("\nWeb interface now running at %s:%d", st.TailscaleIPs[0], web.ListenPort)
|
||||
}
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// calcAdvertiseRoutesForSet returns the new value for Prefs.AdvertiseRoutes based on the
|
||||
|
||||
@@ -26,6 +26,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
|
||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
|
||||
github.com/klauspost/compress/flate from nhooyr.io/websocket
|
||||
💣 github.com/mattn/go-colorable from tailscale.com/cmd/tailscale/cli
|
||||
💣 github.com/mattn/go-isatty from github.com/mattn/go-colorable+
|
||||
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
|
||||
@@ -37,6 +38,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
github.com/peterbourgon/ff/v3 from github.com/peterbourgon/ff/v3/ffcli
|
||||
github.com/peterbourgon/ff/v3/ffcli from tailscale.com/cmd/tailscale/cli
|
||||
github.com/peterbourgon/ff/v3/internal from github.com/peterbourgon/ff/v3
|
||||
github.com/pkg/errors from github.com/gorilla/csrf
|
||||
github.com/skip2/go-qrcode from tailscale.com/cmd/tailscale/cli
|
||||
github.com/skip2/go-qrcode/bitset from github.com/skip2/go-qrcode+
|
||||
github.com/skip2/go-qrcode/reedsolomon from github.com/skip2/go-qrcode
|
||||
@@ -61,13 +63,12 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
💣 go4.org/mem from tailscale.com/derp+
|
||||
go4.org/netipx from tailscale.com/wgengine/filter+
|
||||
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
|
||||
gopkg.in/yaml.v2 from sigs.k8s.io/yaml
|
||||
k8s.io/client-go/util/homedir from tailscale.com/cmd/tailscale/cli
|
||||
nhooyr.io/websocket from tailscale.com/derp/derphttp+
|
||||
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
|
||||
nhooyr.io/websocket/internal/util from nhooyr.io/websocket
|
||||
nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
|
||||
sigs.k8s.io/yaml from tailscale.com/cmd/tailscale/cli
|
||||
sigs.k8s.io/yaml/goyaml.v2 from sigs.k8s.io/yaml
|
||||
software.sslmate.com/src/go-pkcs12 from tailscale.com/cmd/tailscale/cli
|
||||
software.sslmate.com/src/go-pkcs12/internal/rc2 from software.sslmate.com/src/go-pkcs12
|
||||
tailscale.com from tailscale.com/version
|
||||
@@ -142,7 +143,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/util/cloudenv from tailscale.com/net/dnscache+
|
||||
tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy+
|
||||
tailscale.com/util/cmpx from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/util/ctxkey from tailscale.com/types/logger
|
||||
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
|
||||
tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/util/groupmember from tailscale.com/client/web
|
||||
@@ -157,7 +157,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/util/set from tailscale.com/health+
|
||||
tailscale.com/util/singleflight from tailscale.com/net/dnscache+
|
||||
tailscale.com/util/slicesx from tailscale.com/net/dnscache+
|
||||
tailscale.com/util/syspolicy from tailscale.com/ipn
|
||||
tailscale.com/util/testenv from tailscale.com/cmd/tailscale/cli
|
||||
tailscale.com/util/truncate from tailscale.com/cmd/tailscale/cli
|
||||
tailscale.com/util/vizerror from tailscale.com/types/ipproto+
|
||||
@@ -267,7 +266,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
image/png from github.com/skip2/go-qrcode
|
||||
io from bufio+
|
||||
io/fs from crypto/x509+
|
||||
io/ioutil from github.com/godbus/dbus/v5+
|
||||
io/ioutil from golang.org/x/sys/cpu+
|
||||
log from expvar+
|
||||
log/internal from log
|
||||
maps from tailscale.com/types/views+
|
||||
@@ -297,7 +296,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
regexp from github.com/tailscale/goupnp/httpu+
|
||||
regexp/syntax from regexp
|
||||
runtime/debug from tailscale.com/util/singleflight+
|
||||
runtime/trace from testing
|
||||
slices from tailscale.com/cmd/tailscale/cli+
|
||||
sort from compress/flate+
|
||||
strconv from compress/flate+
|
||||
@@ -305,7 +303,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
sync from compress/flate+
|
||||
sync/atomic from context+
|
||||
syscall from crypto/rand+
|
||||
testing from tailscale.com/util/syspolicy
|
||||
text/tabwriter from github.com/peterbourgon/ff/v3/ffcli+
|
||||
text/template from html/template
|
||||
text/template/parse from html/template+
|
||||
|
||||
@@ -6,11 +6,11 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
||||
LD github.com/anmitsu/go-shlex from tailscale.com/tempfork/gliderlabs/ssh
|
||||
L github.com/aws/aws-sdk-go-v2 from github.com/aws/aws-sdk-go-v2/internal/ini
|
||||
L github.com/aws/aws-sdk-go-v2/aws from github.com/aws/aws-sdk-go-v2/aws/middleware+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/arn from tailscale.com/ipn/store/awsstore
|
||||
L github.com/aws/aws-sdk-go-v2/aws/defaults from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/middleware from github.com/aws/aws-sdk-go-v2/aws/retry+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/middleware/private/metrics from github.com/aws/aws-sdk-go-v2/aws/retry+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/protocol/query from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
L github.com/aws/aws-sdk-go-v2/aws/protocol/restjson from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/protocol/xml from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
@@ -30,9 +30,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/aws/aws-sdk-go-v2/feature/ec2/imds from github.com/aws/aws-sdk-go-v2/config+
|
||||
L github.com/aws/aws-sdk-go-v2/feature/ec2/imds/internal/config from github.com/aws/aws-sdk-go-v2/feature/ec2/imds
|
||||
L github.com/aws/aws-sdk-go-v2/internal/auth from github.com/aws/aws-sdk-go-v2/aws/signer/v4+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/auth/smithy from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/configsources from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 from github.com/aws/aws-sdk-go-v2/service/ssm/internal/endpoints+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/ini from github.com/aws/aws-sdk-go-v2/config
|
||||
@@ -43,7 +41,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/aws/aws-sdk-go-v2/internal/strings from github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4
|
||||
L github.com/aws/aws-sdk-go-v2/internal/sync/singleflight from github.com/aws/aws-sdk-go-v2/aws
|
||||
L github.com/aws/aws-sdk-go-v2/internal/timeconv from github.com/aws/aws-sdk-go-v2/aws/retry
|
||||
L github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
L github.com/aws/aws-sdk-go-v2/service/internal/presigned-url from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
L github.com/aws/aws-sdk-go-v2/service/ssm from tailscale.com/ipn/store/awsstore
|
||||
L github.com/aws/aws-sdk-go-v2/service/ssm/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/ssm
|
||||
@@ -58,7 +55,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/aws/aws-sdk-go-v2/service/sts/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
L github.com/aws/aws-sdk-go-v2/service/sts/types from github.com/aws/aws-sdk-go-v2/credentials/stscreds+
|
||||
L github.com/aws/smithy-go from github.com/aws/aws-sdk-go-v2/aws/protocol/restjson+
|
||||
L github.com/aws/smithy-go/auth from github.com/aws/aws-sdk-go-v2/internal/auth+
|
||||
L github.com/aws/smithy-go/auth/bearer from github.com/aws/aws-sdk-go-v2/aws+
|
||||
L github.com/aws/smithy-go/context from github.com/aws/smithy-go/auth/bearer
|
||||
L github.com/aws/smithy-go/document from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
@@ -71,7 +67,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/aws/smithy-go/io from github.com/aws/aws-sdk-go-v2/feature/ec2/imds+
|
||||
L github.com/aws/smithy-go/logging from github.com/aws/aws-sdk-go-v2/aws+
|
||||
L github.com/aws/smithy-go/middleware from github.com/aws/aws-sdk-go-v2/aws+
|
||||
L github.com/aws/smithy-go/private/requestcompression from github.com/aws/aws-sdk-go-v2/config
|
||||
L github.com/aws/smithy-go/ptr from github.com/aws/aws-sdk-go-v2/aws+
|
||||
L github.com/aws/smithy-go/rand from github.com/aws/aws-sdk-go-v2/aws/middleware+
|
||||
L github.com/aws/smithy-go/time from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
@@ -113,6 +108,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
|
||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||
github.com/klauspost/compress from github.com/klauspost/compress/zstd
|
||||
github.com/klauspost/compress/flate from nhooyr.io/websocket
|
||||
github.com/klauspost/compress/fse from github.com/klauspost/compress/huff0
|
||||
github.com/klauspost/compress/huff0 from github.com/klauspost/compress/zstd
|
||||
github.com/klauspost/compress/internal/cpuinfo from github.com/klauspost/compress/zstd+
|
||||
@@ -134,6 +130,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/pierrec/lz4/v4/internal/lz4errors from github.com/pierrec/lz4/v4+
|
||||
L github.com/pierrec/lz4/v4/internal/lz4stream from github.com/pierrec/lz4/v4
|
||||
L github.com/pierrec/lz4/v4/internal/xxh32 from github.com/pierrec/lz4/v4/internal/lz4stream
|
||||
github.com/pkg/errors from github.com/gorilla/csrf
|
||||
LD github.com/pkg/sftp from tailscale.com/ssh/tailssh
|
||||
LD github.com/pkg/sftp/internal/encoding/ssh/filexfer from github.com/pkg/sftp
|
||||
L 💣 github.com/safchain/ethtool from tailscale.com/net/netkernelconf
|
||||
@@ -144,7 +141,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
W github.com/tailscale/go-winio/internal/stringbuffer from github.com/tailscale/go-winio/internal/fs
|
||||
W github.com/tailscale/go-winio/pkg/guid from github.com/tailscale/go-winio+
|
||||
github.com/tailscale/golang-x-crypto/acme from tailscale.com/ipn/ipnlocal
|
||||
LD github.com/tailscale/golang-x-crypto/internal/poly1305 from github.com/tailscale/golang-x-crypto/ssh
|
||||
LD github.com/tailscale/golang-x-crypto/chacha20 from github.com/tailscale/golang-x-crypto/ssh
|
||||
LD 💣 github.com/tailscale/golang-x-crypto/internal/alias from github.com/tailscale/golang-x-crypto/chacha20
|
||||
LD github.com/tailscale/golang-x-crypto/ssh from tailscale.com/ipn/ipnlocal+
|
||||
LD github.com/tailscale/golang-x-crypto/ssh/internal/bcrypt_pbkdf from github.com/tailscale/golang-x-crypto/ssh
|
||||
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+
|
||||
@@ -193,13 +191,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
💣 gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack
|
||||
gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/header+
|
||||
gvisor.dev/gvisor/pkg/tcpip/adapters/gonet from tailscale.com/wgengine/netstack
|
||||
💣 gvisor.dev/gvisor/pkg/tcpip/checksum from gvisor.dev/gvisor/pkg/buffer+
|
||||
gvisor.dev/gvisor/pkg/tcpip/checksum from gvisor.dev/gvisor/pkg/buffer+
|
||||
gvisor.dev/gvisor/pkg/tcpip/hash/jenkins from gvisor.dev/gvisor/pkg/tcpip/stack+
|
||||
gvisor.dev/gvisor/pkg/tcpip/header from gvisor.dev/gvisor/pkg/tcpip/header/parse+
|
||||
gvisor.dev/gvisor/pkg/tcpip/header/parse from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
|
||||
gvisor.dev/gvisor/pkg/tcpip/internal/tcp from gvisor.dev/gvisor/pkg/tcpip/stack+
|
||||
gvisor.dev/gvisor/pkg/tcpip/link/channel from tailscale.com/wgengine/netstack
|
||||
gvisor.dev/gvisor/pkg/tcpip/network/hash from gvisor.dev/gvisor/pkg/tcpip/network/ipv4
|
||||
gvisor.dev/gvisor/pkg/tcpip/network/hash from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
|
||||
gvisor.dev/gvisor/pkg/tcpip/network/internal/fragmentation from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
|
||||
gvisor.dev/gvisor/pkg/tcpip/network/internal/ip from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
|
||||
gvisor.dev/gvisor/pkg/tcpip/network/internal/multicast from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
|
||||
@@ -222,7 +220,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
W 💣 inet.af/wf from tailscale.com/wf
|
||||
nhooyr.io/websocket from tailscale.com/derp/derphttp+
|
||||
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
|
||||
nhooyr.io/websocket/internal/util from nhooyr.io/websocket
|
||||
nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
|
||||
tailscale.com from tailscale.com/version
|
||||
tailscale.com/appc from tailscale.com/ipn/ipnlocal
|
||||
@@ -348,11 +345,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/util/cloudenv from tailscale.com/net/dns/resolver+
|
||||
tailscale.com/util/cmpver from tailscale.com/net/dns+
|
||||
tailscale.com/util/cmpx from tailscale.com/derp/derphttp+
|
||||
tailscale.com/util/ctxkey from tailscale.com/ipn/ipnlocal+
|
||||
💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+
|
||||
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics+
|
||||
tailscale.com/util/dnsname from tailscale.com/hostinfo+
|
||||
tailscale.com/util/execqueue from tailscale.com/control/controlclient+
|
||||
tailscale.com/util/goroutines from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/util/groupmember from tailscale.com/ipn/ipnauth+
|
||||
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
||||
@@ -408,10 +403,11 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
|
||||
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
|
||||
golang.org/x/crypto/curve25519 from github.com/tailscale/golang-x-crypto/ssh+
|
||||
LD golang.org/x/crypto/ed25519 from github.com/tailscale/golang-x-crypto/ssh
|
||||
golang.org/x/crypto/hkdf from crypto/tls+
|
||||
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||
golang.org/x/crypto/poly1305 from github.com/tailscale/wireguard-go/device+
|
||||
golang.org/x/crypto/poly1305 from github.com/tailscale/golang-x-crypto/ssh+
|
||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||
LD golang.org/x/crypto/ssh from tailscale.com/ssh/tailssh+
|
||||
golang.org/x/exp/constraints from github.com/dblohm7/wingoes/pe+
|
||||
@@ -495,7 +491,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
hash from crypto+
|
||||
hash/adler32 from compress/zlib+
|
||||
hash/crc32 from compress/gzip+
|
||||
hash/fnv from tailscale.com/wgengine/magicsock
|
||||
hash/fnv from tailscale.com/wgengine/magicsock+
|
||||
hash/maphash from go4.org/mem
|
||||
html from tailscale.com/ipn/ipnlocal+
|
||||
html/template from github.com/gorilla/csrf
|
||||
|
||||
@@ -27,6 +27,7 @@ func configureTaildrop(logf logger.Logf, lb *ipnlocal.LocalBackend) {
|
||||
} else {
|
||||
logf("%s Taildrop: using %v", dg, path)
|
||||
lb.SetDirectFileRoot(path)
|
||||
lb.SetDirectFileDoFinalRename(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ package main // import "tailscale.com/cmd/tailscaled"
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"expvar"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -333,11 +332,13 @@ func run() (err error) {
|
||||
// Parse config, if specified, to fail early if it's invalid.
|
||||
var conf *conffile.Config
|
||||
if args.confFile != "" {
|
||||
logf("loading config file")
|
||||
conf, err = conffile.Load(args.confFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading config file: %w", err)
|
||||
}
|
||||
sys.InitialConfig = conf
|
||||
logf("loaded initial config: %#+v", string(sys.InitialConfig.Raw))
|
||||
}
|
||||
|
||||
var netMon *netmon.Monitor
|
||||
@@ -512,13 +513,7 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
|
||||
return ok
|
||||
}
|
||||
dialer.NetstackDialTCP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) {
|
||||
// Note: don't just return ns.DialContextTCP or we'll
|
||||
// return an interface containing a nil pointer.
|
||||
tcpConn, err := ns.DialContextTCP(ctx, dst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tcpConn, nil
|
||||
return ns.DialContextTCP(ctx, dst)
|
||||
}
|
||||
}
|
||||
if socksListener != nil || httpProxyListener != nil {
|
||||
@@ -730,7 +725,7 @@ func runDebugServer(mux *http.ServeMux, addr string) {
|
||||
}
|
||||
|
||||
func newNetstack(logf logger.Logf, sys *tsd.System) (*netstack.Impl, error) {
|
||||
ret, err := netstack.Create(logf,
|
||||
return netstack.Create(logf,
|
||||
sys.Tun.Get(),
|
||||
sys.Engine.Get(),
|
||||
sys.MagicSock.Get(),
|
||||
@@ -738,14 +733,6 @@ func newNetstack(logf logger.Logf, sys *tsd.System) (*netstack.Impl, error) {
|
||||
sys.DNSManager.Get(),
|
||||
sys.ProxyMapper(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Only register debug info if we have a debug mux
|
||||
if debugMux != nil {
|
||||
expvar.Publish("netstack", ret.ExpVar())
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// mustStartProxyListeners creates listeners for local SOCKS and HTTP
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/types/persist"
|
||||
"tailscale.com/types/structs"
|
||||
"tailscale.com/util/execqueue"
|
||||
)
|
||||
|
||||
type LoginGoal struct {
|
||||
@@ -119,7 +118,7 @@ type Auto struct {
|
||||
closed bool
|
||||
updateCh chan struct{} // readable when we should inform the server of a change
|
||||
observer Observer // called to update Client status; always non-nil
|
||||
observerQueue execqueue.ExecQueue
|
||||
observerQueue execQueue
|
||||
|
||||
unregisterHealthWatch func()
|
||||
|
||||
@@ -676,7 +675,7 @@ func (c *Auto) Shutdown() {
|
||||
direct := c.direct
|
||||
if !closed {
|
||||
c.closed = true
|
||||
c.observerQueue.Shutdown()
|
||||
c.observerQueue.shutdown()
|
||||
c.cancelAuthCtxLocked()
|
||||
c.cancelMapCtxLocked()
|
||||
for _, w := range c.unpauseWaiters {
|
||||
@@ -697,7 +696,7 @@ func (c *Auto) Shutdown() {
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
c.observerQueue.Wait(ctx)
|
||||
c.observerQueue.wait(ctx)
|
||||
c.logf("Client.Shutdown done.")
|
||||
}
|
||||
}
|
||||
@@ -738,3 +737,95 @@ func (c *Auto) DoNoiseRequest(req *http.Request) (*http.Response, error) {
|
||||
func (c *Auto) GetSingleUseNoiseRoundTripper(ctx context.Context) (http.RoundTripper, *tailcfg.EarlyNoise, error) {
|
||||
return c.direct.GetSingleUseNoiseRoundTripper(ctx)
|
||||
}
|
||||
|
||||
type execQueue struct {
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
inFlight bool // whether a goroutine is running q.run
|
||||
doneWaiter chan struct{} // non-nil if waiter is waiting, then closed
|
||||
queue []func()
|
||||
}
|
||||
|
||||
func (q *execQueue) Add(f func()) {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
if q.closed {
|
||||
return
|
||||
}
|
||||
if q.inFlight {
|
||||
q.queue = append(q.queue, f)
|
||||
} else {
|
||||
q.inFlight = true
|
||||
go q.run(f)
|
||||
}
|
||||
}
|
||||
|
||||
// RunSync waits for the queue to be drained and then synchronously runs f.
|
||||
// It returns an error if the queue is closed before f is run or ctx expires.
|
||||
func (q *execQueue) RunSync(ctx context.Context, f func()) error {
|
||||
for {
|
||||
if err := q.wait(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
q.mu.Lock()
|
||||
if q.inFlight {
|
||||
q.mu.Unlock()
|
||||
continue
|
||||
}
|
||||
defer q.mu.Unlock()
|
||||
if q.closed {
|
||||
return errors.New("closed")
|
||||
}
|
||||
f()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (q *execQueue) run(f func()) {
|
||||
f()
|
||||
|
||||
q.mu.Lock()
|
||||
for len(q.queue) > 0 && !q.closed {
|
||||
f := q.queue[0]
|
||||
q.queue[0] = nil
|
||||
q.queue = q.queue[1:]
|
||||
q.mu.Unlock()
|
||||
f()
|
||||
q.mu.Lock()
|
||||
}
|
||||
q.inFlight = false
|
||||
q.queue = nil
|
||||
if q.doneWaiter != nil {
|
||||
close(q.doneWaiter)
|
||||
q.doneWaiter = nil
|
||||
}
|
||||
q.mu.Unlock()
|
||||
}
|
||||
|
||||
func (q *execQueue) shutdown() {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
q.closed = true
|
||||
}
|
||||
|
||||
// wait waits for the queue to be empty.
|
||||
func (q *execQueue) wait(ctx context.Context) error {
|
||||
q.mu.Lock()
|
||||
waitCh := q.doneWaiter
|
||||
if q.inFlight && waitCh == nil {
|
||||
waitCh = make(chan struct{})
|
||||
q.doneWaiter = waitCh
|
||||
}
|
||||
q.mu.Unlock()
|
||||
|
||||
if waitCh == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-waitCh:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1044,7 +1044,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
|
||||
|
||||
var resp tailcfg.MapResponse
|
||||
if err := c.decodeMsg(msg, &resp, machinePrivKey); err != nil {
|
||||
vlogf("netmap: decode error: %v", err)
|
||||
vlogf("netmap: decode error: %v")
|
||||
return err
|
||||
}
|
||||
watchdogTimer.Stop()
|
||||
|
||||
@@ -87,7 +87,6 @@ type mapSession struct {
|
||||
lastPopBrowserURL string
|
||||
lastTKAInfo *tailcfg.TKAInfo
|
||||
lastNetmapSummary string // from NetworkMap.VeryConcise
|
||||
lastMaxExpiry time.Duration
|
||||
}
|
||||
|
||||
// newMapSession returns a mostly unconfigured new mapSession.
|
||||
@@ -320,9 +319,6 @@ func (ms *mapSession) updateStateFromResponse(resp *tailcfg.MapResponse) {
|
||||
if resp.TKAInfo != nil {
|
||||
ms.lastTKAInfo = resp.TKAInfo
|
||||
}
|
||||
if resp.MaxKeyDuration > 0 {
|
||||
ms.lastMaxExpiry = resp.MaxKeyDuration
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -767,7 +763,6 @@ func (ms *mapSession) netmap() *netmap.NetworkMap {
|
||||
DERPMap: ms.lastDERPMap,
|
||||
ControlHealth: ms.lastHealth,
|
||||
TKAEnabled: ms.lastTKAInfo != nil && !ms.lastTKAInfo.Disabled,
|
||||
MaxKeyDuration: ms.lastMaxExpiry,
|
||||
}
|
||||
|
||||
if ms.lastTKAInfo != nil && ms.lastTKAInfo.Head != "" {
|
||||
|
||||
@@ -64,15 +64,6 @@ type Knobs struct {
|
||||
// LinuxForceNfTables is whether the node should use nftables for Linux
|
||||
// netfiltering, unless overridden by the user.
|
||||
LinuxForceNfTables atomic.Bool
|
||||
|
||||
// SeamlessKeyRenewal is whether to enable the alpha functionality of
|
||||
// renewing node keys without breaking connections.
|
||||
// http://go/seamless-key-renewal
|
||||
SeamlessKeyRenewal atomic.Bool
|
||||
|
||||
// ProbeUDPLifetime is whether the node should probe UDP path lifetime on
|
||||
// the tail end of an active direct connection in magicsock.
|
||||
ProbeUDPLifetime atomic.Bool
|
||||
}
|
||||
|
||||
// UpdateFromNodeAttributes updates k (if non-nil) based on the provided self
|
||||
@@ -98,8 +89,6 @@ func (k *Knobs) UpdateFromNodeAttributes(selfNodeAttrs []tailcfg.NodeCapability,
|
||||
silentDisco = has(tailcfg.NodeAttrSilentDisco)
|
||||
forceIPTables = has(tailcfg.NodeAttrLinuxMustUseIPTables)
|
||||
forceNfTables = has(tailcfg.NodeAttrLinuxMustUseNfTables)
|
||||
seamlessKeyRenewal = has(tailcfg.NodeAttrSeamlessKeyRenewal)
|
||||
probeUDPLifetime = has(tailcfg.NodeAttrProbeUDPLifetime)
|
||||
)
|
||||
|
||||
if has(tailcfg.NodeAttrOneCGNATEnable) {
|
||||
@@ -120,8 +109,6 @@ func (k *Knobs) UpdateFromNodeAttributes(selfNodeAttrs []tailcfg.NodeCapability,
|
||||
k.SilentDisco.Store(silentDisco)
|
||||
k.LinuxForceIPTables.Store(forceIPTables)
|
||||
k.LinuxForceNfTables.Store(forceNfTables)
|
||||
k.SeamlessKeyRenewal.Store(seamlessKeyRenewal)
|
||||
k.ProbeUDPLifetime.Store(probeUDPLifetime)
|
||||
}
|
||||
|
||||
// AsDebugJSON returns k as something that can be marshalled with json.Marshal
|
||||
@@ -143,7 +130,5 @@ func (k *Knobs) AsDebugJSON() map[string]any {
|
||||
"SilentDisco": k.SilentDisco.Load(),
|
||||
"LinuxForceIPTables": k.LinuxForceIPTables.Load(),
|
||||
"LinuxForceNfTables": k.LinuxForceNfTables.Load(),
|
||||
"SeamlessKeyRenewal": k.SeamlessKeyRenewal.Load(),
|
||||
"ProbeUDPLifetime": k.ProbeUDPLifetime.Load(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,16 +159,16 @@ func (c *Client) parseServerInfo(b []byte) (*serverInfo, error) {
|
||||
}
|
||||
|
||||
type clientInfo struct {
|
||||
// Version is the DERP protocol version that the client was built with.
|
||||
// See the ProtocolVersion const.
|
||||
Version int `json:"version,omitempty"`
|
||||
|
||||
// MeshKey optionally specifies a pre-shared key used by
|
||||
// trusted clients. It's required to subscribe to the
|
||||
// connection list & forward packets. It's empty for regular
|
||||
// users.
|
||||
MeshKey string `json:"meshKey,omitempty"`
|
||||
|
||||
// Version is the DERP protocol version that the client was built with.
|
||||
// See the ProtocolVersion const.
|
||||
Version int `json:"version,omitempty"`
|
||||
|
||||
// CanAckPings is whether the client declares it's able to ack
|
||||
// pings.
|
||||
CanAckPings bool
|
||||
|
||||
@@ -712,6 +712,7 @@ func (s *Server) accept(ctx context.Context, nc Conn, brw *bufio.ReadWriter, rem
|
||||
bw: bw,
|
||||
logf: logger.WithPrefix(s.logf, fmt.Sprintf("derp client %v%s: ", remoteAddr, clientKey.ShortString())),
|
||||
done: ctx.Done(),
|
||||
remoteAddr: remoteAddr,
|
||||
remoteIPPort: remoteIPPort,
|
||||
connectedAt: s.clock.Now(),
|
||||
sendQueue: make(chan pkt, perClientSendQueueDepth),
|
||||
@@ -1316,6 +1317,7 @@ type sclient struct {
|
||||
info clientInfo
|
||||
logf logger.Logf
|
||||
done <-chan struct{} // closed when connection closes
|
||||
remoteAddr string // usually ip:port from net.Conn.RemoteAddr().String()
|
||||
remoteIPPort netip.AddrPort // zero if remoteAddr is not ip:port.
|
||||
sendQueue chan pkt // packets queued to this client; never closed
|
||||
discoSendQueue chan pkt // important packets queued to this client; never closed
|
||||
@@ -1352,13 +1354,16 @@ type sclient struct {
|
||||
// peerConnState represents whether a peer is connected to the server
|
||||
// or not.
|
||||
type peerConnState struct {
|
||||
ipPort netip.AddrPort // if present, the peer's IP:port
|
||||
peer key.NodePublic
|
||||
present bool
|
||||
ipPort netip.AddrPort // if present, the peer's IP:port
|
||||
}
|
||||
|
||||
// pkt is a request to write a data frame to an sclient.
|
||||
type pkt struct {
|
||||
// src is the who's the sender of the packet.
|
||||
src key.NodePublic
|
||||
|
||||
// enqueuedAt is when a packet was put onto a queue before it was sent,
|
||||
// and is used for reporting metrics on the duration of packets in the queue.
|
||||
enqueuedAt time.Time
|
||||
@@ -1366,9 +1371,6 @@ type pkt struct {
|
||||
// bs is the data packet bytes.
|
||||
// The memory is owned by pkt.
|
||||
bs []byte
|
||||
|
||||
// src is the who's the sender of the packet.
|
||||
src key.NodePublic
|
||||
}
|
||||
|
||||
// peerGoneMsg is a request to write a peerGone frame to an sclient
|
||||
@@ -1571,17 +1573,6 @@ func (c *sclient) sendMeshUpdates() error {
|
||||
c.s.mu.Lock()
|
||||
defer c.s.mu.Unlock()
|
||||
|
||||
// allow all happened-before mesh update request goroutines to complete, if
|
||||
// we don't finish the task we'll queue another below.
|
||||
drainUpdates:
|
||||
for {
|
||||
select {
|
||||
case <-c.meshUpdate:
|
||||
default:
|
||||
break drainUpdates
|
||||
}
|
||||
}
|
||||
|
||||
writes := 0
|
||||
for _, pcs := range c.peerStateChange {
|
||||
if c.bw.Available() <= frameHeaderLen+keyLen {
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<policyDefinitionResources revision="1.0" schemaVersion="1.0"
|
||||
xmlns="http://www.microsoft.com/GroupPolicy/PolicyDefinitions">
|
||||
<displayName>Tailscale</displayName>
|
||||
<description>A set of policies that enforces particular settings in the Tailscale Windows client.</description>
|
||||
<resources>
|
||||
<stringTable>
|
||||
<string id="TAILSCALE_PRODUCT">Tailscale</string>
|
||||
<string id="SINCE_V1_22">Tailscale version 1.22.0 and later</string>
|
||||
<string id="SINCE_V1_26">Tailscale version 1.26.0 and later</string>
|
||||
<string id="SINCE_V1_50">Tailscale version 1.50.0 and later</string>
|
||||
<string id="SINCE_V1_52">Tailscale version 1.52.0 and later</string>
|
||||
<string id="SINCE_V1_56">Tailscale version 1.56.0 and later</string>
|
||||
<string id="PARTIAL_FULL_SINCE_V1_56">Tailscale version 1.56.0 and later (full support), some earlier versions (partial support)</string>
|
||||
<string id="SINCE_V1_58">Tailscale version 1.58.0 and later</string>
|
||||
<string id="Tailscale_Category">Tailscale</string>
|
||||
<string id="UI_Category">UI customization</string>
|
||||
<string id="Settings_Category">Settings</string>
|
||||
<string id="LoginURL">Require using a specific Tailscale coordination server</string>
|
||||
<string id="LoginURL_Help"><![CDATA[This policy can be used to require the use of a particular Tailscale coordination server.
|
||||
See https://tailscale.com/kb/1315/mdm-keys#set-a-custom-control-server-url for more details.
|
||||
|
||||
If you configure this policy, set it to the URL of your coordination server, beginning with https:// and ending with no trailing slash. If blank or "https://controlplane.tailscale.com", the default coordination server will be required.
|
||||
|
||||
If you disable this policy, the Tailscale SaaS coordination server will be used by default, but a non-standard Tailscale coordination server can be configured using the CLI.]]></string>
|
||||
<string id="LogTarget">Require using a specific Tailscale log server</string>
|
||||
<string id="LogTarget_Help"><![CDATA[This policy can be used to require the use of a non-standard log server.
|
||||
Please note that using a non-standard log server will limit Tailscale Support's ability to diagnose problems.
|
||||
|
||||
If you configure this policy, set it to the URL of your log server, beginning with https:// and ending with no trailing slash. If blank or "https://log.tailscale.io", the default log server will be used.
|
||||
|
||||
If you disable this policy, the Tailscale standard log server will be used by default, but a non-standard Tailscale log server can be configured using the TS_LOG_TARGET environment variable.]]></string>
|
||||
<string id="Tailnet">Specify which Tailnet should be used for Login</string>
|
||||
<string id="Tailnet_Help"><![CDATA[This policy can be used to suggest or require a specific tailnet when opening the login page.
|
||||
See https://tailscale.com/kb/1315/mdm-keys#set-a-suggested-or-required-tailnet for more details.
|
||||
|
||||
To suggest a tailnet at login time, set this to the name of the tailnet, as shown in the top-left of the admin panel, such as "example.com". That tailnet's SSO button will be shown prominently, along with the option to select a different tailnet.
|
||||
|
||||
To require logging in to a particular tailnet, add the "required:" prefix, such as "required:example.com". The result is similar to the suggested tailnet but there will be no option to choose a different tailnet.
|
||||
|
||||
If you configure this policy, set it to the name of the tailnet, possibly with the "required:" prefix, as described above.
|
||||
|
||||
If you disable this policy, the standard login page will be used.]]></string>
|
||||
<string id="ExitNodeID">Require using a specific Exit Node</string>
|
||||
<string id="ExitNodeID_Help"><![CDATA[This policy can be used to require always using the specified Exit Node whenever the Tailscale client is connected.
|
||||
See https://tailscale.com/kb/1315/mdm-keys#force-an-exit-node-to-always-be-used and https://tailscale.com/kb/1103/exit-nodes for more details.
|
||||
|
||||
If you enable this policy, set it to the ID of an exit node. The ID is visible on the Machines page of the admin console, or can be queried using the Tailscale API. If the specified exit node is unavailable, this device will have no Internet access unless Tailscale is disconnected.
|
||||
|
||||
If you disable this policy or supply an empty exit node ID, then usage of exit nodes will be disallowed.
|
||||
|
||||
If you do not configure this policy, no exit node will be used by default but an exit node (if one is available and permitted by ACLs) can be chosen by the user if desired.]]></string>
|
||||
<string id="AllowIncomingConnections">Allow incoming connections</string>
|
||||
<string id="AllowIncomingConnections_Help"><![CDATA[This policy can be used to require that the Allow Incoming Connections setting is configured a certain way.
|
||||
See https://tailscale.com/kb/1315/mdm-keys#set-whether-to-allow-incoming-connections and https://tailscale.com/kb/1072/client-preferences#allow-incoming-connections for more details.
|
||||
|
||||
If you enable this policy, then Allow Incoming Connections is always enabled and the menu option is hidden.
|
||||
|
||||
If you disable this policy, then Allow Incoming Connections is always disabled and the menu option is hidden.
|
||||
|
||||
If you do not configure this policy, then Allow Incoming Connections depends on what is selected in the Preferences submenu.]]></string>
|
||||
<string id="UnattendedMode">Run Tailscale in Unattended Mode</string>
|
||||
<string id="UnattendedMode_Help"><![CDATA[This policy can be used to require that the Run Unattended setting is configured a certain way.
|
||||
See https://tailscale.com/kb/1315/mdm-keys#set-unattended-mode and https://tailscale.com/kb/1088/run-unattended for more details.
|
||||
|
||||
If you enable this policy, then Run Unattended is always enabled and the menu option is hidden.
|
||||
|
||||
If you disable this policy, then Run Unattended is always disabled and the menu option is hidden.
|
||||
|
||||
If you do not configure this policy, then Run Unattended depends on what is selected in the Preferences submenu.]]></string>
|
||||
<string id="ExitNodeAllowLANAccess">Allow Local Network Access when an Exit Node is in use</string>
|
||||
<string id="ExitNodeAllowLANAccess_Help"><![CDATA[This policy can be used to require that the Allow Local Network Access setting is configured a certain way.
|
||||
See https://tailscale.com/kb/1315/mdm-keys#toggle-local-network-access-when-an-exit-node-is-in-use and https://tailscale.com/kb/1103/exit-nodes#step-4-use-the-exit-node for more details.
|
||||
|
||||
If you enable this policy, then Allow Local Network Access is always enabled and the menu option is hidden.
|
||||
|
||||
If you disable this policy, then Allow Local Network Access is always disabled and the menu option is hidden.
|
||||
|
||||
If you do not configure this policy, then Allow Local Network Access depends on what is selected in the Exit Node submenu.]]></string>
|
||||
<string id="UseTailscaleDNSSettings">Use Tailscale DNS Settings</string>
|
||||
<string id="UseTailscaleDNSSettings_Help"><![CDATA[This policy can be used to require that Use Tailscale DNS is configured a certain way.
|
||||
See https://tailscale.com/kb/1315/mdm-keys#set-whether-the-device-uses-tailscale-dns-settings for more details.
|
||||
|
||||
If you enable this policy, then Use Tailscale DNS is always enabled and the menu option is hidden.
|
||||
|
||||
If you disable this policy, then Use Tailscale DNS is always disabled and the menu option is hidden.
|
||||
|
||||
If you do not configure this policy, then Use Tailscale DNS depends on what is selected in the Preferences submenu.]]></string>
|
||||
<string id="UseTailscaleSubnets">Use Tailscale Subnets</string>
|
||||
<string id="UseTailscaleSubnets_Help"><![CDATA[This policy can be used to require that Use Tailscale Subnets is configured a certain way.
|
||||
See https://tailscale.com/kb/1315/mdm-keys#set-whether-the-device-accepts-tailscale-subnets or https://tailscale.com/kb/1019/subnets for more details.
|
||||
|
||||
If you enable this policy, then Use Tailscale Subnets is always enabled and the menu option is hidden.
|
||||
|
||||
If you disable this policy, then Use Tailscale Subnets is always disabled and the menu option is hidden.
|
||||
|
||||
If you do not configure this policy, then Use Tailscale Subnets depends on what is selected in the Preferences submenu.]]></string>
|
||||
<string id="InstallUpdates">Automatically install updates</string>
|
||||
<string id="InstallUpdates_Help"><![CDATA[This policy can be used to require that Automatically Install Updates is configured a certain way.
|
||||
See https://tailscale.com/kb/1067/update#auto-updates for more details.
|
||||
|
||||
If you enable this policy, then Automatically Install Updates is always enabled and the menu option is hidden.
|
||||
|
||||
If you disable this policy, then Automatically Install Updates is always disabled and the menu option is hidden.
|
||||
|
||||
If you do not configure this policy, then Automatically Install Updates depends on what is selected in the Preferences submenu.]]></string>
|
||||
<string id="AdvertiseExitNode">Run Tailscale as an Exit Node</string>
|
||||
<string id="AdvertiseExitNode_Help"><![CDATA[This policy can be used to require that Run Exit Node is configured a certain way.
|
||||
See https://tailscale.com/kb/1103/exit-nodes for more details.
|
||||
|
||||
If you enable this policy, then Run Exit Node is always enabled and the menu option is hidden.
|
||||
|
||||
If you disable this policy, then Run Exit Node is always disabled and the menu option is hidden.
|
||||
|
||||
If you do not configure this policy, then Run Exit Node depends on what is selected in the Exit Node submenu.]]></string>
|
||||
<string id="AdminPanel">Show the "Admin Panel" menu item</string>
|
||||
<string id="AdminPanel_Help"><![CDATA[This policy can be used to show or hide the Admin Console item in the Tailscale Menu.
|
||||
|
||||
If you enable or don't configure this policy, the Admin Console item will be shown in the Tailscale menu when available.
|
||||
|
||||
If you disable this policy, the Admin Console item will always be hidden from the Tailscale menu.]]></string>
|
||||
<string id="NetworkDevices">Show the "Network Devices" submenu</string>
|
||||
<string id="NetworkDevices_Help"><![CDATA[This policy can be used to show or hide the Network Devices submenu in the Tailscale Menu.
|
||||
|
||||
If you enable or don't configure this policy, the Network Devices submenu will be shown in the Tailscale menu.
|
||||
|
||||
If you disable this policy, the Network Devices submenu will be hidden from the Tailscale menu. This does not affect other devices' visibility in the CLI.]]></string>
|
||||
<string id="TestMenu">Show the "Debug" submenu</string>
|
||||
<string id="TestMenu_Help"><![CDATA[This policy can be used to show or hide the Debug submenu of the Tailscale menu.
|
||||
See https://tailscale.com/kb/1315/mdm-keys#hide-the-debug-menu for more details.
|
||||
|
||||
If you enable or don't configure this policy, the Debug submenu will be shown in the Tailscale menu when opened while holding Ctrl.
|
||||
|
||||
If you disable this policy, the Debug submenu will be hidden from the Tailscale menu.]]></string>
|
||||
<string id="UpdateMenu">Show the "Update Available" menu item</string>
|
||||
<string id="UpdateMenu_Help"><![CDATA[This policy can be used to show or hide the Update Available item in the Tailscale Menu.
|
||||
See https://tailscale.com/kb/1315/mdm-keys#hide-the-update-menu for more details.
|
||||
|
||||
If you enable or don't configure this policy, the Update Available item will be shown in the Tailscale menu when there is an update.
|
||||
|
||||
If you disable this policy, the Update Available item will be hidden from the Tailscale menu.]]></string>
|
||||
<string id="RunExitNode">Show the "Run Exit Node" menu item</string>
|
||||
<string id="RunExitNode_Help"><![CDATA[This policy can be used to show or hide the Run Exit Node item in the Exit Node submenu.
|
||||
See https://tailscale.com/kb/1315/mdm-keys#hide-the-run-as-exit-node-menu-item for more details.
|
||||
This does not affect using the CLI to enable or disable advertising an exit node. If you wish to enable or disable this feature, see the Run Exit Node policy in the Settings category.
|
||||
|
||||
If you enable or don't configure this policy, the Run Exit Node item will be shown in the Exit Node submenu.
|
||||
|
||||
If you disable this policy, the Run Exit Node item will be hidden from the Exit Node submenu.]]></string>
|
||||
<string id="PreferencesMenu">Show the "Preferences" submenu</string>
|
||||
<string id="PreferencesMenu_Help"><![CDATA[This policy can be used to show or hide the Preferences submenu of the Tailscale menu.
|
||||
See https://tailscale.com/kb/1315/mdm-keys#hide-the-preferences-menu for more details.
|
||||
This does not affect using the CLI to modify that menu's preferences. If you wish to control those, look at the policies in the Settings category.
|
||||
|
||||
If you enable or don't configure this policy, the Preferences submenu will be shown in the Tailscale menu.
|
||||
|
||||
If you disable this policy, the Preferences submenu will be hidden from the Tailscale menu.]]></string>
|
||||
<string id="ExitNodesPicker">Show the "Exit Node" submenu</string>
|
||||
<string id="ExitNodesPicker_Help"><![CDATA[This policy can be used to show or hide the Exit Node submenu of the Tailscale menu.
|
||||
See https://tailscale.com/kb/1315/mdm-keys#hide-the-exit-node-picker for more details.
|
||||
This does not affect using the CLI to select or stop using an exit node. If you wish to control exit node usage, look at the "Require using a specific Exit Node" policy in the Settings category.
|
||||
|
||||
If you enable or don't configure this policy, the Exit Node submenu will be shown in the Tailscale menu.
|
||||
|
||||
If you disable this policy, the Exit Node submenu will be hidden from the Tailscale menu.]]></string>
|
||||
<string id="KeyExpirationNoticeTime">Specify a custom key expiration notification time</string>
|
||||
<string id="KeyExpirationNoticeTime_Help"><![CDATA[This policy can be used to configure how soon the notification appears before key expiry.
|
||||
See https://tailscale.com/kb/1315/mdm-keys#set-the-key-expiration-notice-period for more details.
|
||||
|
||||
Time intervals must be specified as a Go Duration: for example, 24h, 5h25m30s. Time units larger than hours are unsupported.
|
||||
|
||||
If you enable this policy and supply a valid time interval, the key expiry notification will begin to display when the current key has less than that amount of time remaining.
|
||||
|
||||
If you disable or don't configure this policy, the default time period will be used (as of Tailscale 1.56, this is 24 hours).]]></string>
|
||||
<string id="LogSCMInteractions">Log extra details about service events</string>
|
||||
<string id="LogSCMInteractions_Help"><![CDATA[This policy can be used to enable additional logging related to Service Control Manager for debugging purposes.
|
||||
This should only be enabled if recommended by Tailscale Support.
|
||||
|
||||
If you enable this policy, additional logging will be enabled for SCM events.
|
||||
|
||||
If you disable or don't configure this policy, the normal amount of logging occurs.]]></string>
|
||||
<string id="FlushDNSOnSessionUnlock">Flush the DNS cache on session unlock</string>
|
||||
<string id="FlushDNSOnSessionUnlock_Help"><![CDATA[This policy can be used to enable additional DNS cache flushing for debugging purposes.
|
||||
This should only be enabled if recommended by Tailscale Support.
|
||||
|
||||
If you enable this policy, the DNS cache will be flushed on session unlock in addition to when the DNS cache would normally be flushed.
|
||||
|
||||
If you disable or don't configure this policy, the DNS cache is managed normally.]]></string>
|
||||
<string id="PostureChecking">Collect data for posture checking</string>
|
||||
<string id="PostureChecking_Help"><![CDATA[This policy can be used to require that the Posture Checking setting is configured a certain way.
|
||||
See https://tailscale.com/kb/1315/mdm-keys#enable-gathering-device-posture-data and https://tailscale.com/kb/1326/device-identity for more details.
|
||||
|
||||
If you enable this policy, then data collection is always enabled.
|
||||
|
||||
If you disable this policy, then data collection is always disabled.
|
||||
|
||||
If you do not configure this policy, then data collection depends on if it has been enabled from the CLI (as of Tailscale 1.56), it may be present in the GUI in later versions.]]></string>
|
||||
</stringTable>
|
||||
<presentationTable>
|
||||
<presentation id="LoginURL">
|
||||
<textBox refId="LoginURLPrompt">
|
||||
<label>Coordination server</label>
|
||||
</textBox>
|
||||
</presentation>
|
||||
<presentation id="LogTarget">
|
||||
<textBox refId="LogTargetPrompt">
|
||||
<label>Log server</label>
|
||||
</textBox>
|
||||
</presentation>
|
||||
<presentation id="Tailnet">
|
||||
<textBox refId="TailnetPrompt">
|
||||
<label>Tailnet</label>
|
||||
</textBox>
|
||||
</presentation>
|
||||
<presentation id="ExitNodeID">
|
||||
<textBox refId="ExitNodeIDPrompt">
|
||||
<label>Exit Node</label>
|
||||
</textBox>
|
||||
</presentation>
|
||||
</presentationTable>
|
||||
</resources>
|
||||
</policyDefinitionResources>
|
||||
@@ -1,256 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<policyDefinitions revision="1.0" schemaVersion="1.0"
|
||||
xmlns="http://www.microsoft.com/GroupPolicy/PolicyDefinitions">
|
||||
<policyNamespaces>
|
||||
<target prefix="tailscale" namespace="Tailscale.Policies" />
|
||||
</policyNamespaces>
|
||||
<resources minRequiredRevision="1.0" />
|
||||
|
||||
<supportedOn>
|
||||
<products>
|
||||
<product name="TAILSCALE_PRODUCT" displayName="$(string.TAILSCALE_PRODUCT)">
|
||||
<majorVersion name="TAILSCALE_V1" displayName="$(string.TAILSCALE_PRODUCT)" versionIndex="1" />
|
||||
</product>
|
||||
</products>
|
||||
|
||||
<definitions>
|
||||
<definition name="SINCE_V1_22"
|
||||
displayName="$(string.SINCE_V1_22)">
|
||||
<and><reference ref="TAILSCALE_PRODUCT"/></and>
|
||||
</definition>
|
||||
<definition name="SINCE_V1_26"
|
||||
displayName="$(string.SINCE_V1_26)">
|
||||
<and><reference ref="TAILSCALE_PRODUCT"/></and>
|
||||
</definition>
|
||||
<definition name="SINCE_V1_50"
|
||||
displayName="$(string.SINCE_V1_50)">
|
||||
<and><reference ref="TAILSCALE_PRODUCT"/></and>
|
||||
</definition>
|
||||
<definition name="SINCE_V1_52"
|
||||
displayName="$(string.SINCE_V1_52)">
|
||||
<and><reference ref="TAILSCALE_PRODUCT"/></and>
|
||||
</definition>
|
||||
<definition name="PARTIAL_FULL_SINCE_V1_56"
|
||||
displayName="$(string.PARTIAL_FULL_SINCE_V1_56)">
|
||||
<and><reference ref="TAILSCALE_PRODUCT"/></and>
|
||||
</definition>
|
||||
<definition name="SINCE_V1_56"
|
||||
displayName="$(string.SINCE_V1_56)">
|
||||
<and><reference ref="TAILSCALE_PRODUCT"/></and>
|
||||
</definition>
|
||||
<definition name="SINCE_V1_58"
|
||||
displayName="$(string.SINCE_V1_58)">
|
||||
<and><reference ref="TAILSCALE_PRODUCT"/></and>
|
||||
</definition>
|
||||
</definitions>
|
||||
</supportedOn>
|
||||
<categories>
|
||||
<category name="Top_Category" displayName="$(string.Tailscale_Category)" />
|
||||
<category name="UI_Category" displayName="$(string.UI_Category)">
|
||||
<parentCategory ref="Top_Category" />
|
||||
</category>
|
||||
<category name="Settings_Category" displayName="$(string.Settings_Category)">
|
||||
<parentCategory ref="Top_Category" />
|
||||
</category>
|
||||
</categories>
|
||||
<policies>
|
||||
<policy name="LoginURL" class="Machine" displayName="$(string.LoginURL)" explainText="$(string.LoginURL_Help)" presentation="$(presentation.LoginURL)" key="Software\Policies\Tailscale">
|
||||
<parentCategory ref="Top_Category" />
|
||||
<supportedOn ref="PARTIAL_FULL_SINCE_V1_56" />
|
||||
<elements>
|
||||
<text id="LoginURLPrompt" valueName="LoginURL" required="true" />
|
||||
</elements>
|
||||
</policy>
|
||||
<policy name="LogTarget" class="Machine" displayName="$(string.LogTarget)" explainText="$(string.LogTarget_Help)" presentation="$(presentation.LogTarget)" key="Software\Policies\Tailscale">
|
||||
<parentCategory ref="Top_Category" />
|
||||
<supportedOn ref="SINCE_V1_58" />
|
||||
<elements>
|
||||
<text id="LogTargetPrompt" valueName="LogTarget" required="true" />
|
||||
</elements>
|
||||
</policy>
|
||||
<policy name="Tailnet" class="Machine" displayName="$(string.Tailnet)" explainText="$(string.Tailnet_Help)" presentation="$(presentation.Tailnet)" key="Software\Policies\Tailscale">
|
||||
<parentCategory ref="Top_Category" />
|
||||
<supportedOn ref="SINCE_V1_52" />
|
||||
<elements>
|
||||
<text id="TailnetPrompt" valueName="Tailnet" required="true" />
|
||||
</elements>
|
||||
</policy>
|
||||
<policy name="ExitNodeID" class="Machine" displayName="$(string.ExitNodeID)" explainText="$(string.ExitNodeID_Help)" presentation="$(presentation.ExitNodeID)" key="Software\Policies\Tailscale">
|
||||
<parentCategory ref="Settings_Category" />
|
||||
<supportedOn ref="SINCE_V1_56" />
|
||||
<elements>
|
||||
<text id="ExitNodeIDPrompt" valueName="ExitNodeID" required="true" />
|
||||
</elements>
|
||||
</policy>
|
||||
<policy name="AllowIncomingConnections" class="Machine" displayName="$(string.AllowIncomingConnections)" explainText="$(string.AllowIncomingConnections_Help)" key="Software\Policies\Tailscale" valueName="AllowIncomingConnections">
|
||||
<parentCategory ref="Settings_Category" />
|
||||
<supportedOn ref="PARTIAL_FULL_SINCE_V1_56" />
|
||||
<enabledValue>
|
||||
<string>always</string>
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<string>never</string>
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="UnattendedMode" class="Machine" displayName="$(string.UnattendedMode)" explainText="$(string.UnattendedMode_Help)" key="Software\Policies\Tailscale" valueName="UnattendedMode">
|
||||
<parentCategory ref="Settings_Category" />
|
||||
<supportedOn ref="PARTIAL_FULL_SINCE_V1_56" />
|
||||
<enabledValue>
|
||||
<string>always</string>
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<string>never</string>
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="ExitNodeAllowLANAccess" class="Machine" displayName="$(string.ExitNodeAllowLANAccess)" explainText="$(string.ExitNodeAllowLANAccess_Help)" key="Software\Policies\Tailscale" valueName="ExitNodeAllowLANAccess">
|
||||
<parentCategory ref="Settings_Category" />
|
||||
<supportedOn ref="PARTIAL_FULL_SINCE_V1_56" />
|
||||
<enabledValue>
|
||||
<string>always</string>
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<string>never</string>
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="UseTailscaleDNSSettings" class="Machine" displayName="$(string.UseTailscaleDNSSettings)" explainText="$(string.UseTailscaleDNSSettings_Help)" key="Software\Policies\Tailscale" valueName="UseTailscaleDNSSettings">
|
||||
<parentCategory ref="Settings_Category" />
|
||||
<supportedOn ref="PARTIAL_FULL_SINCE_V1_56" />
|
||||
<enabledValue>
|
||||
<string>always</string>
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<string>never</string>
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="UseTailscaleSubnets" class="Machine" displayName="$(string.UseTailscaleSubnets)" explainText="$(string.UseTailscaleSubnets_Help)" key="Software\Policies\Tailscale" valueName="UseTailscaleSubnets">
|
||||
<parentCategory ref="Settings_Category" />
|
||||
<supportedOn ref="PARTIAL_FULL_SINCE_V1_56" />
|
||||
<enabledValue>
|
||||
<string>always</string>
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<string>never</string>
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="InstallUpdates" class="Machine" displayName="$(string.InstallUpdates)" explainText="$(string.InstallUpdates_Help)" key="Software\Policies\Tailscale" valueName="InstallUpdates">
|
||||
<parentCategory ref="Settings_Category" />
|
||||
<supportedOn ref="PARTIAL_FULL_SINCE_V1_56" />
|
||||
<enabledValue>
|
||||
<string>always</string>
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<string>never</string>
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="AdvertiseExitNode" class="Machine" displayName="$(string.AdvertiseExitNode)" explainText="$(string.AdvertiseExitNode_Help)" key="Software\Policies\Tailscale" valueName="AdvertiseExitNode">
|
||||
<parentCategory ref="Settings_Category" />
|
||||
<supportedOn ref="PARTIAL_FULL_SINCE_V1_56" />
|
||||
<enabledValue>
|
||||
<string>always</string>
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<string>never</string>
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="PostureChecking" class="Machine" displayName="$(string.PostureChecking)" explainText="$(string.PostureChecking_Help)" key="Software\Policies\Tailscale" valueName="PostureChecking">
|
||||
<parentCategory ref="Settings_Category" />
|
||||
<supportedOn ref="PARTIAL_FULL_SINCE_V1_56" />
|
||||
<enabledValue>
|
||||
<string>always</string>
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<string>never</string>
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="LogSCMInteractions" class="Machine" displayName="$(string.LogSCMInteractions)" explainText="$(string.LogSCMInteractions_Help)" key="Software\Policies\Tailscale" valueName="LogSCMInteractions">
|
||||
<parentCategory ref="Top_Category" />
|
||||
<supportedOn ref="SINCE_V1_26" />
|
||||
<enabledValue>
|
||||
<decimal value="1" />
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<decimal value="0" />
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="FlushDNSOnSessionUnlock" class="Machine" displayName="$(string.FlushDNSOnSessionUnlock)" explainText="$(string.FlushDNSOnSessionUnlock_Help)" key="Software\Policies\Tailscale" valueName="FlushDNSOnSessionUnlock">
|
||||
<parentCategory ref="Top_Category" />
|
||||
<supportedOn ref="SINCE_V1_22" />
|
||||
<enabledValue>
|
||||
<decimal value="1" />
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<decimal value="0" />
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="AdminPanel" class="Machine" displayName="$(string.AdminPanel)" explainText="$(string.AdminPanel_Help)" key="Software\Policies\Tailscale" valueName="AdminPanel">
|
||||
<parentCategory ref="UI_Category" />
|
||||
<supportedOn ref="SINCE_V1_22" />
|
||||
<enabledValue>
|
||||
<string>show</string>
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<string>hide</string>
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="NetworkDevices" class="Machine" displayName="$(string.NetworkDevices)" explainText="$(string.NetworkDevices_Help)" key="Software\Policies\Tailscale" valueName="NetworkDevices">
|
||||
<parentCategory ref="UI_Category" />
|
||||
<supportedOn ref="SINCE_V1_22" />
|
||||
<enabledValue>
|
||||
<string>show</string>
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<string>hide</string>
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="TestMenu" class="Machine" displayName="$(string.TestMenu)" explainText="$(string.TestMenu_Help)" key="Software\Policies\Tailscale" valueName="TestMenu">
|
||||
<parentCategory ref="UI_Category" />
|
||||
<supportedOn ref="SINCE_V1_22" />
|
||||
<enabledValue>
|
||||
<string>show</string>
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<string>hide</string>
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="UpdateMenu" class="Machine" displayName="$(string.UpdateMenu)" explainText="$(string.UpdateMenu_Help)" key="Software\Policies\Tailscale" valueName="UpdateMenu">
|
||||
<parentCategory ref="UI_Category" />
|
||||
<supportedOn ref="SINCE_V1_22" />
|
||||
<enabledValue>
|
||||
<string>show</string>
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<string>hide</string>
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="RunExitNode" class="Machine" displayName="$(string.RunExitNode)" explainText="$(string.RunExitNode_Help)" key="Software\Policies\Tailscale" valueName="RunExitNode">
|
||||
<parentCategory ref="UI_Category" />
|
||||
<supportedOn ref="SINCE_V1_22" />
|
||||
<enabledValue>
|
||||
<string>show</string>
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<string>hide</string>
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="PreferencesMenu" class="Machine" displayName="$(string.PreferencesMenu)" explainText="$(string.PreferencesMenu_Help)" key="Software\Policies\Tailscale" valueName="PreferencesMenu">
|
||||
<parentCategory ref="UI_Category" />
|
||||
<supportedOn ref="SINCE_V1_22" />
|
||||
<enabledValue>
|
||||
<string>show</string>
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<string>hide</string>
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="ExitNodesPicker" class="Machine" displayName="$(string.ExitNodesPicker)" explainText="$(string.ExitNodesPicker_Help)" key="Software\Policies\Tailscale" valueName="ExitNodesPicker">
|
||||
<parentCategory ref="UI_Category" />
|
||||
<supportedOn ref="SINCE_V1_22" />
|
||||
<enabledValue>
|
||||
<string>show</string>
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<string>hide</string>
|
||||
</disabledValue>
|
||||
</policy>
|
||||
</policies>
|
||||
</policyDefinitions>
|
||||
@@ -120,4 +120,4 @@
|
||||
in
|
||||
flake-utils.lib.eachDefaultSystem (system: flakeForSystem nixpkgs system);
|
||||
}
|
||||
# nix-direnv cache busting line: sha256-b/iffKOn7nMiWvM0AIGGzZaJ15NTaBlJff+aja3NQio=
|
||||
# nix-direnv cache busting line: sha256-uMVRdgO/HTs0CKqWPUFEL/rFvzio1vblTUaz5Cgi+5Q=
|
||||
|
||||
175
go.mod
175
go.mod
@@ -1,130 +1,127 @@
|
||||
module tailscale.com
|
||||
|
||||
go 1.21.1
|
||||
|
||||
toolchain go1.21.5
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
filippo.io/mkcert v1.4.4
|
||||
github.com/akutz/memconn v0.1.0
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa
|
||||
github.com/andybalholm/brotli v1.1.0
|
||||
github.com/andybalholm/brotli v1.0.5
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.5
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.42
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.64
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.0
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.38.0
|
||||
github.com/coreos/go-iptables v0.7.0
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
github.com/coreos/go-systemd/v22 v22.5.0
|
||||
github.com/creack/pty v1.1.21
|
||||
github.com/creack/pty v1.1.18
|
||||
github.com/dave/courtney v0.4.0
|
||||
github.com/dave/jennifer v1.7.0
|
||||
github.com/dave/patsy v0.0.0-20210517141501-957256f50cba
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa
|
||||
github.com/dblohm7/wingoes v0.0.0-20230929194252-e994401fc077
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e
|
||||
github.com/dsnet/try v0.0.3
|
||||
github.com/evanw/esbuild v0.19.11
|
||||
github.com/frankban/quicktest v1.14.6
|
||||
github.com/evanw/esbuild v0.19.4
|
||||
github.com/frankban/quicktest v1.14.5
|
||||
github.com/fxamacker/cbor/v2 v2.5.0
|
||||
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0
|
||||
github.com/go-logr/zapr v1.3.0
|
||||
github.com/go-json-experiment/json v0.0.0-20230922184908-dc36ffcf8533
|
||||
github.com/go-logr/zapr v1.2.4
|
||||
github.com/go-ole/go-ole v1.3.0
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
|
||||
github.com/golangci/golangci-lint v1.52.2
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/go-containerregistry v0.18.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/go-containerregistry v0.17.0
|
||||
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/goreleaser/nfpm/v2 v2.33.1
|
||||
github.com/hdevalence/ed25519consensus v0.2.0
|
||||
github.com/hdevalence/ed25519consensus v0.1.0
|
||||
github.com/iancoleman/strcase v0.3.0
|
||||
github.com/illarion/gonotify v1.0.1
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a
|
||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
|
||||
github.com/jsimonetti/rtnetlink v1.4.0
|
||||
github.com/jsimonetti/rtnetlink v1.3.5
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.17.4
|
||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-isatty v0.0.19
|
||||
github.com/mdlayher/genetlink v1.3.2
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/mdlayher/sdnotify v1.0.0
|
||||
github.com/miekg/dns v1.1.58
|
||||
github.com/miekg/dns v1.1.56
|
||||
github.com/mitchellh/go-ps v1.0.0
|
||||
github.com/peterbourgon/ff/v3 v3.4.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/sftp v1.13.6
|
||||
github.com/prometheus/client_golang v1.18.0
|
||||
github.com/prometheus/common v0.46.0
|
||||
github.com/prometheus/client_golang v1.17.0
|
||||
github.com/prometheus/common v0.44.0
|
||||
github.com/safchain/ethtool v0.3.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e
|
||||
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502
|
||||
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20240108194725-7ce1f622c780
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20230713185742-f0b76a10a08e
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05
|
||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
|
||||
github.com/tailscale/mkctr v0.0.0-20240102155253-bf50773ba734
|
||||
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20240111230031-5ca22df9e6e7
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20231213172531-a4fa669015b2
|
||||
github.com/tailscale/wireguard-go v0.0.0-20231121184858-cc193a0b3272
|
||||
github.com/tc-hib/winres v0.2.1
|
||||
github.com/tcnksm/go-httpstat v0.2.0
|
||||
github.com/toqueteos/webbrowser v1.2.0
|
||||
github.com/u-root/u-root v0.12.0
|
||||
github.com/u-root/u-root v0.11.0
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||
github.com/vishvananda/netns v0.0.4
|
||||
go.uber.org/zap v1.26.0
|
||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.18.0
|
||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925
|
||||
golang.org/x/crypto v0.15.0
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
||||
golang.org/x/mod v0.14.0
|
||||
golang.org/x/net v0.20.0
|
||||
golang.org/x/oauth2 v0.16.0
|
||||
golang.org/x/sync v0.6.0
|
||||
golang.org/x/sys v0.16.0
|
||||
golang.org/x/term v0.16.0
|
||||
golang.org/x/time v0.5.0
|
||||
golang.org/x/tools v0.17.0
|
||||
golang.org/x/net v0.18.0
|
||||
golang.org/x/oauth2 v0.12.0
|
||||
golang.org/x/sync v0.5.0
|
||||
golang.org/x/sys v0.15.0
|
||||
golang.org/x/term v0.14.0
|
||||
golang.org/x/time v0.3.0
|
||||
golang.org/x/tools v0.15.0
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
gvisor.dev/gvisor v0.0.0-20240119233241-c9c1d4f9b186
|
||||
gvisor.dev/gvisor v0.0.0-20230928000133-4fe30062272c
|
||||
honnef.co/go/tools v0.4.6
|
||||
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
|
||||
inet.af/tcpproxy v0.0.0-20231102063150-2862066fc2a9
|
||||
inet.af/wf v0.0.0-20221017222439-36129f591884
|
||||
k8s.io/api v0.29.1
|
||||
k8s.io/apimachinery v0.29.1
|
||||
k8s.io/apiserver v0.29.1
|
||||
k8s.io/client-go v0.29.1
|
||||
nhooyr.io/websocket v1.8.10
|
||||
k8s.io/api v0.28.2
|
||||
k8s.io/apimachinery v0.28.2
|
||||
k8s.io/apiserver v0.28.2
|
||||
k8s.io/client-go v0.28.2
|
||||
nhooyr.io/websocket v1.8.7
|
||||
sigs.k8s.io/controller-runtime v0.16.2
|
||||
sigs.k8s.io/controller-tools v0.13.0
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/dave/astrid v0.0.0-20170323122508-8c2895878b14 // indirect
|
||||
github.com/dave/brenda v1.1.0 // indirect
|
||||
github.com/gobuffalo/flect v1.0.2 // indirect
|
||||
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
|
||||
4d63.com/gochecknoglobals v0.2.1 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
filippo.io/edwards25519 v1.0.0 // indirect
|
||||
github.com/Abirdcfly/dupword v0.0.11 // indirect
|
||||
github.com/AlekSi/pointer v1.2.0 // indirect
|
||||
github.com/Antonboom/errname v0.1.9 // indirect
|
||||
@@ -137,26 +134,27 @@ require (
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/OpenPeeDeeP/depguard v1.1.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/alexkohler/prealloc v1.0.0 // indirect
|
||||
github.com/alingse/asasalint v0.0.11 // indirect
|
||||
github.com/ashanbrown/forbidigo v1.5.1 // indirect
|
||||
github.com/ashanbrown/makezero v1.1.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.40 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
|
||||
github.com/aws/smithy-go v1.19.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 // indirect
|
||||
github.com/aws/smithy-go v1.14.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bkielbasa/cyclop v1.2.0 // indirect
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
|
||||
@@ -169,35 +167,35 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/charithe/durationcheck v0.0.10 // indirect
|
||||
github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/cloudflare/circl v1.3.3 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect
|
||||
github.com/curioswitch/go-reassign v0.2.0 // indirect
|
||||
github.com/daixiang0/gci v0.10.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/denis-tingaikin/go-header v0.4.3 // indirect
|
||||
github.com/docker/cli v25.0.0+incompatible // indirect
|
||||
github.com/docker/cli v24.0.7+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker v25.0.0+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.2 // indirect
|
||||
github.com/docker/docker v24.0.7+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/esimonov/ifshort v1.0.4 // indirect
|
||||
github.com/ettle/strcase v0.1.1 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.8.1 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.7.0 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/firefart/nonamedreturns v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/go-critic/go-critic v0.8.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||
github.com/go-git/go-git/v5 v5.11.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.2 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.22.7 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.4.1 // indirect
|
||||
github.com/go-git/go-git/v5 v5.8.1 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/go-toolsmith/astcast v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astcopy v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astequal v1.1.0 // indirect
|
||||
@@ -226,7 +224,7 @@ require (
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 // indirect
|
||||
github.com/goreleaser/chglog v0.5.0 // indirect
|
||||
github.com/goreleaser/fileglob v1.3.0 // indirect
|
||||
github.com/gorilla/csrf v1.7.2
|
||||
github.com/gorilla/csrf v1.7.1
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
|
||||
github.com/gostaticanalysis/comment v1.4.2 // indirect
|
||||
github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect
|
||||
@@ -269,6 +267,7 @@ require (
|
||||
github.com/maratori/testpackage v1.1.1 // indirect
|
||||
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
|
||||
github.com/mdlayher/socket v0.5.0 // indirect
|
||||
github.com/mgechev/revive v1.3.1 // indirect
|
||||
@@ -288,14 +287,14 @@ require (
|
||||
github.com/nunnatsa/ginkgolinter v0.11.2 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc6 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.18 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/polyfloyd/go-errorlint v1.4.1 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/quasilyte/go-ruleguard v0.3.19 // indirect
|
||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
||||
@@ -320,8 +319,8 @@ require (
|
||||
github.com/sonatard/noctx v0.0.2 // indirect
|
||||
github.com/sourcegraph/go-diff v0.7.0 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/cobra v1.8.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/cobra v1.7.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.16.0 // indirect
|
||||
@@ -338,7 +337,7 @@ require (
|
||||
github.com/timonwong/loggercheck v0.9.4 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
||||
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
|
||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/ultraware/funlen v0.0.3 // indirect
|
||||
github.com/ultraware/whitespace v0.0.5 // indirect
|
||||
@@ -351,27 +350,27 @@ require (
|
||||
gitlab.com/bosi/decorder v0.2.3 // indirect
|
||||
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a // indirect
|
||||
golang.org/x/image v0.15.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/image v0.12.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.29.1 // indirect
|
||||
k8s.io/component-base v0.29.1 // indirect
|
||||
k8s.io/klog/v2 v2.120.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20240117194847-208609032b15 // indirect
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect
|
||||
k8s.io/apiextensions-apiserver v0.28.2 // indirect
|
||||
k8s.io/component-base v0.28.2 // indirect
|
||||
k8s.io/klog/v2 v2.100.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20230928205116-a78145627833 // indirect
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
|
||||
mvdan.cc/gofumpt v0.5.0 // indirect
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
|
||||
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1 +1 @@
|
||||
sha256-b/iffKOn7nMiWvM0AIGGzZaJ15NTaBlJff+aja3NQio=
|
||||
sha256-uMVRdgO/HTs0CKqWPUFEL/rFvzio1vblTUaz5Cgi+5Q=
|
||||
|
||||
410
go.sum
410
go.sum
@@ -42,8 +42,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
|
||||
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
|
||||
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
|
||||
github.com/Abirdcfly/dupword v0.0.11 h1:z6v8rMETchZXUIuHxYNmlUAuKuB21PeaSymTed16wgU=
|
||||
@@ -78,12 +78,14 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/OpenPeeDeeP/depguard v1.1.1 h1:TSUznLjvp/4IUP+OQ0t/4jF4QUyxIcVX8YnghZdunyA=
|
||||
github.com/OpenPeeDeeP/depguard v1.1.1/go.mod h1:JtAMzWkmFEzDPyAd+W0NHl1lvpQKTvT9jnRVsohBKpc=
|
||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs=
|
||||
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
|
||||
github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
|
||||
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -97,8 +99,8 @@ github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pO
|
||||
github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE=
|
||||
github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=
|
||||
github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU=
|
||||
@@ -109,58 +111,58 @@ github.com/ashanbrown/forbidigo v1.5.1/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1
|
||||
github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s=
|
||||
github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc=
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.22/go.mod h1:mN7Li1wxaPxSSy4Xkr6stFuinJGf3VZW3ZSNvO0q6sI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.42 h1:28jHROB27xZwU0CB88giDSjz7M1Sba3olb5JBGwina8=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.42/go.mod h1:4AZM3nMMxwlG+eZlxvBKqwVbkDLlnN2a4UGTL6HjaZI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.21/go.mod h1:90Dk1lJoMyspa/EDUrldTxsPns0wn6+KpRKpdAWc0uA=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.40 h1:s8yOkDh+5b1jUDhMBtngF6zKWLDs84chUk2Vk0c38Og=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.40/go.mod h1:VtEHVAAqDWASwdOqj/1huyT6uHbs5s8FUHfDQdky/Rs=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 h1:uDZJF1hu0EVT/4bogChk8DyjSF6fof6uL/0Y26Ma7Fg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11/go.mod h1:TEPP4tENqBGO99KwVpV9MlOX4NSrSLP8u3KRy2CDwA8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.64 h1:9QJQs36z61YB8nxGwRDfWXEDYbU6H7jdI6zFiAX1vag=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.64/go.mod h1:4Q7R9MFpXRdjO3YnAfUTdnuENs32WzBkASt6VxSYDYQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34/go.mod h1:Etz2dj6UHYuw+Xw830KfzCfWGMzqvUTCjUj5b76GVDc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 h1:g+qlObJH4Kn4n21g69DjspU0hKTjWtq7naZ9OLCv0ew=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43/go.mod h1:rzfdUlfA+jdgLDmPKjd3Chq9V7LVLYo1Nz++Wb91aRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25 h1:AzwRi5OKKwo4QNqPf7TjeO+tK8AyOK3GVSwmRPo7/Cs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25/go.mod h1:SUbB4wcbSEyCvqBxv/O/IBf93RbEze7U7OnoTlpPB+g=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28 h1:vGWm5vTpMr39tEZfQeDiDAMgk+5qsnvRny3FjLpnH5w=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28/go.mod h1:spfrICMD6wCAhjhzHuy6DOZZ+LAIY10UxhUmLzpJTTs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2 h1:NbWkRxEEIRSCqxhsHQuMiTH7yo+JZW1gp8v3elSVMTQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2/go.mod h1:4tfW5l4IAB32VWCDEBxCRtR9T4BWy4I4kr1spr8NgZM=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.0 h1:L5h2fymEdVJYvn6hYO8Jx48YmC6xVmjmgHJV3oGKgmc=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.0/go.mod h1:J9kLNzEiHSeGMyN7238EjJmBpCniVzFda75Gxl/NqB8=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.38.0 h1:JON9MBvwUlM8HXylfB2caZuH3VXz9RxO4SMp2+TNc3Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.38.0/go.mod h1:JjBzoceyKkpQY3v1GPIdg6kHqUFHRJ7SDlwtwoH0Qh8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.9/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 h1:YkNzx1RLS0F5qdf9v1Q8Cuv9NXCL2TkosOxhzlUPV64=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 h1:8lKOidPkmSmfUtiTgtdXWgaKItCZ/g75/jEk6Ql6GsA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1/go.mod h1:yygr8ACQRY2PrEcy3xsUI357stq2AxnFM6DIsR9lij4=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.10/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 h1:s4bioTgjSFRwOoyEFzAVCmFmoowBgjTR8gkrF/sQ4wk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU=
|
||||
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
|
||||
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
|
||||
github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ=
|
||||
github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@@ -198,12 +200,11 @@ github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8/go.mod h1:gakxgyXa
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
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.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
|
||||
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
@@ -215,14 +216,12 @@ github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pq
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
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/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo=
|
||||
github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/daixiang0/gci v0.10.1 h1:eheNA3ljF6SxnPD/vE4lCBusVHmV3Rs3dkKvFrJ7MR0=
|
||||
github.com/daixiang0/gci v0.10.1/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI=
|
||||
github.com/dave/astrid v0.0.0-20170323122508-8c2895878b14 h1:YI1gOOdmMk3xodBao7fehcvoZsEeOyy/cfhlpCSPgM4=
|
||||
@@ -238,26 +237,26 @@ github.com/dave/patsy v0.0.0-20210517141501-957256f50cba/go.mod h1:qfR88CgEGLoiq
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
|
||||
github.com/dblohm7/wingoes v0.0.0-20230929194252-e994401fc077 h1:WphxHslVftszsr0oZOHPaOjpmN/BsgNYF+gW/hxZXXc=
|
||||
github.com/dblohm7/wingoes v0.0.0-20230929194252-e994401fc077/go.mod h1:6NCrWM5jRefaG7iN0iMShPalLsljHWBh9v1zxM2f8Xs=
|
||||
github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU=
|
||||
github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c=
|
||||
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/docker/cli v25.0.0+incompatible h1:zaimaQdnX7fYWFqzN88exE9LDEvRslexpFowZBX6GoQ=
|
||||
github.com/docker/cli v25.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg=
|
||||
github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v25.0.0+incompatible h1:g9b6wZTblhMgzOT2tspESstfw6ySZ9kdm94BLDKaZac=
|
||||
github.com/docker/docker v25.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo=
|
||||
github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
|
||||
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
|
||||
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
|
||||
github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI=
|
||||
github.com/dsnet/try v0.0.3/go.mod h1:WBM8tRpUmnXXhY1U6/S8dt6UWdHTQ7y8A5YSkRCkq40=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/emicklei/go-restful/v3 v3.11.2 h1:1onLa9DcsMYO9P+CXaL0dStDqQ2EHHXLiz+BtnqkLAU=
|
||||
github.com/emicklei/go-restful/v3 v3.11.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
|
||||
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -272,24 +271,28 @@ github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw=
|
||||
github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY=
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch/v5 v5.8.1 h1:iPEdwg0XayoS+E7Mth9JxwUtOgyVxnDTXHtKhZPlZxA=
|
||||
github.com/evanphx/json-patch/v5 v5.8.1/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
|
||||
github.com/evanw/esbuild v0.19.11 h1:mbPO1VJ/df//jjUd+p/nRLYCpizXxXb2w/zZMShxa2k=
|
||||
github.com/evanw/esbuild v0.19.11/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc=
|
||||
github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
|
||||
github.com/evanw/esbuild v0.19.4 h1:Etk+6ZCjtNxZZLEgMKSqpO0/oM0k1WYKJabaPMJ39iQ=
|
||||
github.com/evanw/esbuild v0.19.4/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||
github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y=
|
||||
github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
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/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
||||
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
||||
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
@@ -298,35 +301,45 @@ github.com/go-critic/go-critic v0.8.0 h1:4zOcpvDoKvBOl+R1W81IBznr78f8YaE4zKXkfDV
|
||||
github.com/go-critic/go-critic v0.8.0/go.mod h1:5TjdkPI9cu/yKbYS96BTsslihjKd6zg6vd8O9RZXj2s=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
|
||||
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
|
||||
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
|
||||
github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4=
|
||||
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
|
||||
github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A=
|
||||
github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
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-json-experiment/json v0.0.0-20230922184908-dc36ffcf8533 h1:1SRqDZauC9fz6vMIDLCUOULPNfOnZ0rmvZo8quraoy4=
|
||||
github.com/go-json-experiment/json v0.0.0-20230922184908-dc36ffcf8533/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
|
||||
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
|
||||
github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
|
||||
github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
|
||||
github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU=
|
||||
github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4=
|
||||
github.com/go-openapi/swag v0.22.7 h1:JWrc1uc/P9cSomxfnsFSVWoE1FW6bNbrVPmpQYpCcR8=
|
||||
github.com/go-openapi/swag v0.22.7/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
|
||||
github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
|
||||
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
@@ -354,6 +367,12 @@ github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA
|
||||
github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
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/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
||||
@@ -431,11 +450,10 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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-containerregistry v0.18.0 h1:ShE7erKNPqRh5ue6Z9DUOlk04WsnFWPO6YGr3OxnfoQ=
|
||||
github.com/google/go-containerregistry v0.18.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
|
||||
github.com/google/go-containerregistry v0.17.0 h1:5p+zYs/R4VGHkhyvgWurWrpJ2hW4Vv9fQI+GzdcwXLk=
|
||||
github.com/google/go-containerregistry v0.17.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -463,8 +481,8 @@ github.com/google/rpmpack v0.5.0 h1:L16KZ3QvkFGpYhmp23iQip+mx1X39foEsqszjMNBm8A=
|
||||
github.com/google/rpmpack v0.5.0/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
@@ -478,10 +496,13 @@ github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+
|
||||
github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU=
|
||||
github.com/goreleaser/nfpm/v2 v2.33.1 h1:EkdAzZyVhAI9JC1vjmjjbmnNzyH1J6Cu4JCsA7YcQuc=
|
||||
github.com/goreleaser/nfpm/v2 v2.33.1/go.mod h1:8wwWWvJWmn84xo/Sqiv0aMvEGTHlHZTXTEuVSgQpkIM=
|
||||
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
|
||||
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE=
|
||||
github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=
|
||||
github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado=
|
||||
@@ -506,15 +527,13 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
||||
github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU=
|
||||
github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f h1:ov45/OzrJG8EKbGjn7jJZQJTN7Z1t73sFYNIRd64YlI=
|
||||
github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f/go.mod h1:JoDrYMZpDPYo6uH9/f6Peqms3zNNWT2XiGgioMOIGuI=
|
||||
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
|
||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
@@ -526,8 +545,8 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a h1:S33o3djA1nPRd+d/bf7jbbXytXuK/EoXow7+aa76grQ=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@@ -547,9 +566,10 @@ github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/ra
|
||||
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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
|
||||
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
|
||||
github.com/jsimonetti/rtnetlink v1.3.5 h1:hVlNQNRlLDGZz31gBPicsG7Q53rnlsz1l1Ix/9XlpVA=
|
||||
github.com/jsimonetti/rtnetlink v1.3.5/go.mod h1:0LFedyiTkebnd43tE4YAkWGIq9jQphow4CcwxaT2Y00=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
@@ -575,6 +595,7 @@ github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kkHAIKE/contextcheck v1.1.4 h1:B6zAaLhOEEcjvUgIYEqystmnFk1Oemn8bvJhbt0GMb8=
|
||||
github.com/kkHAIKE/contextcheck v1.1.4/go.mod h1:1+i/gWqokIa+dm31mqGLZhZJ7Uh44DJGZVmr6QRBNJg=
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
@@ -587,6 +608,7 @@ github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -603,6 +625,8 @@ github.com/ldez/gomoddirectives v0.2.3 h1:y7MBaisZVDYmKvt9/l1mjNCiSA1BVn34U0ObUc
|
||||
github.com/ldez/gomoddirectives v0.2.3/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0=
|
||||
github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo=
|
||||
github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt8ivzU=
|
||||
github.com/leonklingele/grouper v1.1.1/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY=
|
||||
github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM=
|
||||
@@ -617,17 +641,21 @@ github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1r
|
||||
github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc=
|
||||
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE=
|
||||
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
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-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo=
|
||||
github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc=
|
||||
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
||||
@@ -640,8 +668,8 @@ github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI
|
||||
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
|
||||
github.com/mgechev/revive v1.3.1 h1:OlQkcH40IB2cGuprTPcjB0iIUddgVZgGmDX3IAMR8D4=
|
||||
github.com/mgechev/revive v1.3.1/go.mod h1:YlD6TTWl2B8A103R9KWJSPVI9DrEf+oqr15q21Ld+5I=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
|
||||
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
@@ -673,6 +701,7 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6Fx
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
|
||||
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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nishanths/exhaustive v0.10.0 h1:BMznKAcVa9WOoLq/kTGp4NJOJSMwEpcpjFNAVRfPlSo=
|
||||
github.com/nishanths/exhaustive v0.10.0/go.mod h1:IbwrGdVMizvDcIxPYGVdQn5BqWJaOwpCvg4RGb8r/TA=
|
||||
github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=
|
||||
@@ -685,14 +714,14 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
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/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||
github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k=
|
||||
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
@@ -704,8 +733,8 @@ github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNc
|
||||
github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc=
|
||||
github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
|
||||
@@ -727,20 +756,20 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
||||
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
||||
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
|
||||
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y=
|
||||
github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ=
|
||||
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
@@ -815,10 +844,10 @@ github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag07
|
||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
@@ -859,8 +888,8 @@ github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
|
||||
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns=
|
||||
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ=
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20240108194725-7ce1f622c780 h1:U0J2CUrrTcc2wmr9tSLYEo+USfwNikRRsmxVLD4eZ7E=
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20240108194725-7ce1f622c780/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ=
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20230713185742-f0b76a10a08e h1:JyeJF/HuSwvxWtsR1c0oKX1lzaSH5Wh4aX+MgiStaGQ=
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20230713185742-f0b76a10a08e/go.mod h1:DjoeCULdP6vTJ/xY+nzzR9LaUHprkbZEpNidX0aqEEk=
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
|
||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
|
||||
@@ -869,8 +898,8 @@ github.com/tailscale/mkctr v0.0.0-20240102155253-bf50773ba734 h1:93cvKHbvsPK3MKf
|
||||
github.com/tailscale/mkctr v0.0.0-20240102155253-bf50773ba734/go.mod h1:6v53VHLmLKUaqWMpSGDeRWhltLSCEteMItYoiKLpdJk=
|
||||
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/web-client-prebuilt v0.0.0-20240111230031-5ca22df9e6e7 h1:xAgOVncJuuxkFZ2oXXDKFTH4HDdFYSZRYdA6oMrCewg=
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20240111230031-5ca22df9e6e7/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20231213172531-a4fa669015b2 h1:lR1voET3dwe3CxacGAiva4k08TXtQ6Dlmult4JILlj4=
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20231213172531-a4fa669015b2/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20231121184858-cc193a0b3272 h1:zwsem4CaamMdC3tFoTpzrsUSMDPV0K6rhnQdF7kXekQ=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20231121184858-cc193a0b3272/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
|
||||
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
||||
@@ -895,12 +924,16 @@ github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
|
||||
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
|
||||
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
|
||||
github.com/u-root/gobusybox/src v0.0.0-20231228173702-b69f654846aa h1:unMPGGK/CRzfg923allsikmvk2l7beBeFPUNC4RVX/8=
|
||||
github.com/u-root/gobusybox/src v0.0.0-20231228173702-b69f654846aa/go.mod h1:Zj4Tt22fJVn/nz/y6Ergm1SahR9dio1Zm/D2/S0TmXM=
|
||||
github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs=
|
||||
github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI=
|
||||
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/gobusybox/src v0.0.0-20221229083637-46b2883a7f90 h1:zTk5683I9K62wtZ6eUa6vu6IWwVHXPnoKK5n2unAwv0=
|
||||
github.com/u-root/gobusybox/src v0.0.0-20221229083637-46b2883a7f90/go.mod h1:lYt+LVfZBBwDZ3+PHk4k/c/TnKOkjJXiJO73E32Mmpc=
|
||||
github.com/u-root/u-root v0.11.0 h1:6gCZLOeRyevw7gbTwMj3fKxnr9+yHFlgF3N7udUVNO8=
|
||||
github.com/u-root/u-root v0.11.0/go.mod h1:DBkDtiZyONk9hzVEdB/PWI9B4TxDkElWlVTHseglrZY=
|
||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg=
|
||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA=
|
||||
@@ -943,16 +976,20 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8=
|
||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
|
||||
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-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -967,8 +1004,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -979,16 +1016,16 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
|
||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a h1:8qmSSA8Gz/1kTrCe0nqR0R3Gb/NDhykzWw2q2mWZydM=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9 h1:j3D9DvWRpUfIyFfDPws7LoIZ2MAI1OJHdQXtTnYtN+k=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
|
||||
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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -1063,8 +1100,8 @@ golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1075,8 +1112,8 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
|
||||
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -1090,8 +1127,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1110,6 +1147,7 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1151,6 +1189,7 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1158,8 +1197,8 @@ golang.org/x/sys v0.4.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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -1168,8 +1207,8 @@ golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
|
||||
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1184,13 +1223,14 @@ golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.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.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -1264,8 +1304,8 @@ golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
|
||||
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1368,8 +1408,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
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=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -1401,8 +1441,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||
gvisor.dev/gvisor v0.0.0-20240119233241-c9c1d4f9b186 h1:VWRSJX9ghfqsRSZGMAILL6QpYRKWnHcYPi24SCubQRs=
|
||||
gvisor.dev/gvisor v0.0.0-20240119233241-c9c1d4f9b186/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
||||
gvisor.dev/gvisor v0.0.0-20230928000133-4fe30062272c h1:bYb98Ra11fJ8F2xFbZx0zg2VQ28lYqC1JxfaaF53xqY=
|
||||
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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -1420,24 +1460,24 @@ inet.af/tcpproxy v0.0.0-20231102063150-2862066fc2a9 h1:zomTWJvjwLbKRgGameQtpK6DN
|
||||
inet.af/tcpproxy v0.0.0-20231102063150-2862066fc2a9/go.mod h1:Tojt5kmHpDIR2jMojxzZK2w2ZR7OILODmUo2gaSwjrk=
|
||||
inet.af/wf v0.0.0-20221017222439-36129f591884 h1:zg9snq3Cpy50lWuVqDYM7AIRVTtU50y5WXETMFohW/Q=
|
||||
inet.af/wf v0.0.0-20221017222439-36129f591884/go.mod h1:bSAQ38BYbY68uwpasXOTZo22dKGy9SNvI6PZFeKomZE=
|
||||
k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw=
|
||||
k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ=
|
||||
k8s.io/apiextensions-apiserver v0.29.1 h1:S9xOtyk9M3Sk1tIpQMu9wXHm5O2MX6Y1kIpPMimZBZw=
|
||||
k8s.io/apiextensions-apiserver v0.29.1/go.mod h1:zZECpujY5yTW58co8V2EQR4BD6A9pktVgHhvc0uLfeU=
|
||||
k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc=
|
||||
k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU=
|
||||
k8s.io/apiserver v0.29.1 h1:e2wwHUfEmMsa8+cuft8MT56+16EONIEK8A/gpBSco+g=
|
||||
k8s.io/apiserver v0.29.1/go.mod h1:V0EpkTRrJymyVT3M49we8uh2RvXf7fWC5XLB0P3SwRw=
|
||||
k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A=
|
||||
k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks=
|
||||
k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw=
|
||||
k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc=
|
||||
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
|
||||
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20240117194847-208609032b15 h1:m6dl1pkxz3HuE2mP9MUYPCCGyy6IIFlv/vTlLBDxIwA=
|
||||
k8s.io/kube-openapi v0.0.0-20240117194847-208609032b15/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw=
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ=
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw=
|
||||
k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg=
|
||||
k8s.io/apiextensions-apiserver v0.28.2 h1:J6/QRWIKV2/HwBhHRVITMLYoypCoPY1ftigDM0Kn+QU=
|
||||
k8s.io/apiextensions-apiserver v0.28.2/go.mod h1:5tnkxLGa9nefefYzWuAlWZ7RZYuN/765Au8cWLA6SRg=
|
||||
k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ=
|
||||
k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU=
|
||||
k8s.io/apiserver v0.28.2 h1:rBeYkLvF94Nku9XfXyUIirsVzCzJBs6jMn3NWeHieyI=
|
||||
k8s.io/apiserver v0.28.2/go.mod h1:f7D5e8wH8MWcKD7azq6Csw9UN+CjdtXIVQUyUhrtb+E=
|
||||
k8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY=
|
||||
k8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY=
|
||||
k8s.io/component-base v0.28.2 h1:Yc1yU+6AQSlpJZyvehm/NkJBII72rzlEsd6MkBQ+G0E=
|
||||
k8s.io/component-base v0.28.2/go.mod h1:4IuQPQviQCg3du4si8GpMrhAIegxpsgPngPRR/zWpzc=
|
||||
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
|
||||
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-openapi v0.0.0-20230928205116-a78145627833 h1:iFFEmmB7szQhJP42AvRD2+gzdVP7EuIKY1rJgxf0JZY=
|
||||
k8s.io/kube-openapi v0.0.0-20230928205116-a78145627833/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E=
|
||||
mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js=
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I=
|
||||
@@ -1446,8 +1486,8 @@ mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphD
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
||||
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 h1:VuJo4Mt0EVPychre4fNlDWDuE5AjXtPJpRUWqZDQhaI=
|
||||
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8/go.mod h1:Oh/d7dEtzsNHGOq1Cdv8aMm3KdKhVvPbRQcM8WFpBR8=
|
||||
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.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
@@ -1457,9 +1497,9 @@ sigs.k8s.io/controller-tools v0.13.0 h1:NfrvuZ4bxyolhDBt/rCZhDnx3M2hzlhgo5n3Iv2R
|
||||
sigs.k8s.io/controller-tools v0.13.0/go.mod h1:5vw3En2NazbejQGCeWKRrE7q4P+CW8/klfVqP8QZkgA=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.1 h1:tbT1jjaeFOF230tzOIRJ6U5S1jNqpsSyNjzDd58H3J8=
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.1/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||
|
||||
@@ -1 +1 @@
|
||||
ea90ced9ddc95c09aed7d9c59631aa978022c3ba
|
||||
7592922bcb2af4e0b49bc67d7923fbdabe168dab
|
||||
|
||||
@@ -175,7 +175,6 @@ type PartialFile struct {
|
||||
// in-progress '*.partial' file's path when the peerapi isn't
|
||||
// being used; see LocalBackend.SetDirectFileRoot.
|
||||
PartialPath string `json:",omitempty"`
|
||||
FinalPath string `json:",omitempty"`
|
||||
|
||||
// Done is set in "direct" mode when the partial file has been
|
||||
// closed and is ready for the caller to rename away the
|
||||
|
||||
@@ -46,8 +46,6 @@ type WindowsToken interface {
|
||||
// IsElevated reports whether the receiver is currently executing as an
|
||||
// elevated administrative user.
|
||||
IsElevated() bool
|
||||
// IsLocalSystem reports whether the receiver is the built-in SYSTEM user.
|
||||
IsLocalSystem() bool
|
||||
// UserDir returns the special directory identified by folderID as associated
|
||||
// with the receiver. folderID must be one of the KNOWNFOLDERID values from
|
||||
// the x/sys/windows package, serialized as a stringified GUID.
|
||||
|
||||
@@ -93,12 +93,6 @@ func (t *token) IsElevated() bool {
|
||||
return t.t.IsElevated()
|
||||
}
|
||||
|
||||
func (t *token) IsLocalSystem() bool {
|
||||
// https://web.archive.org/web/2024/https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers
|
||||
const systemUID = ipn.WindowsUserID("S-1-5-18")
|
||||
return t.IsUID(systemUID)
|
||||
}
|
||||
|
||||
func (t *token) UserDir(folderID string) (string, error) {
|
||||
guid, err := windows.GUIDFromString(folderID)
|
||||
if err != nil {
|
||||
|
||||
@@ -265,8 +265,9 @@ type LocalBackend struct {
|
||||
// It's also used on several NAS platforms (Synology, TrueNAS, etc)
|
||||
// but in that case DoFinalRename is also set true, which moves the
|
||||
// *.partial file to its final name on completion.
|
||||
directFileRoot string
|
||||
componentLogUntil map[string]componentLogState
|
||||
directFileRoot string
|
||||
directFileDoFinalRename bool // false on macOS, true on several NAS platforms
|
||||
componentLogUntil map[string]componentLogState
|
||||
// c2nUpdateStatus is the status of c2n-triggered client update.
|
||||
c2nUpdateStatus updateStatus
|
||||
currentUser ipnauth.WindowsToken
|
||||
@@ -340,6 +341,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
|
||||
}
|
||||
|
||||
if sys.InitialConfig != nil {
|
||||
log.Printf("Found initial config")
|
||||
p := pm.CurrentPrefs().AsStruct()
|
||||
mp, err := sys.InitialConfig.Parsed.ToPrefs()
|
||||
if err != nil {
|
||||
@@ -539,6 +541,17 @@ func (b *LocalBackend) SetDirectFileRoot(dir string) {
|
||||
b.directFileRoot = dir
|
||||
}
|
||||
|
||||
// SetDirectFileDoFinalRename sets whether the peerapi file server should rename
|
||||
// a received "name.partial" file to "name" when the download is complete.
|
||||
//
|
||||
// This only applies when SetDirectFileRoot is non-empty.
|
||||
// The default is false.
|
||||
func (b *LocalBackend) SetDirectFileDoFinalRename(v bool) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.directFileDoFinalRename = v
|
||||
}
|
||||
|
||||
// ReloadConfig reloads the backend's config from disk.
|
||||
//
|
||||
// It returns (false, nil) if not running in declarative mode, (true, nil) on
|
||||
@@ -1062,11 +1075,9 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
|
||||
b.blockEngineUpdates(false)
|
||||
}
|
||||
|
||||
if st.LoginFinished() && (wasBlocked || b.seamlessRenewalEnabled()) {
|
||||
if wasBlocked {
|
||||
// Auth completed, unblock the engine
|
||||
b.blockEngineUpdates(false)
|
||||
}
|
||||
if st.LoginFinished() && wasBlocked {
|
||||
// Auth completed, unblock the engine
|
||||
b.blockEngineUpdates(false)
|
||||
b.authReconfig()
|
||||
b.send(ipn.Notify{LoginFinished: &empty.Message{}})
|
||||
}
|
||||
@@ -1098,7 +1109,7 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
|
||||
b.authURL = st.URL
|
||||
b.authURLSticky = st.URL
|
||||
}
|
||||
if (wasBlocked || b.seamlessRenewalEnabled()) && st.LoginFinished() {
|
||||
if wasBlocked && st.LoginFinished() {
|
||||
// Interactive login finished successfully (URL visited).
|
||||
// After an interactive login, the user always wants
|
||||
// WantRunning.
|
||||
@@ -2446,10 +2457,8 @@ func (b *LocalBackend) popBrowserAuthNow() {
|
||||
|
||||
b.logf("popBrowserAuthNow: url=%v", url != "")
|
||||
|
||||
if !b.seamlessRenewalEnabled() {
|
||||
b.blockEngineUpdates(true)
|
||||
b.stopEngineAndWait()
|
||||
}
|
||||
b.blockEngineUpdates(true)
|
||||
b.stopEngineAndWait()
|
||||
b.tellClientToBrowseToURL(url)
|
||||
if b.State() == ipn.Running {
|
||||
b.enterState(ipn.Starting)
|
||||
@@ -2735,16 +2744,6 @@ func (b *LocalBackend) CheckIPNConnectionAllowed(ci *ipnauth.ConnIdentity) error
|
||||
if !b.pm.CurrentPrefs().ForceDaemon() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Always allow Windows SYSTEM user to connect,
|
||||
// even if Tailscale is currently being used by another user.
|
||||
if tok, err := ci.WindowsToken(); err == nil {
|
||||
defer tok.Close()
|
||||
if tok.IsLocalSystem() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
uid := ci.WindowsUserID()
|
||||
if uid == "" {
|
||||
return errors.New("empty user uid in connection identity")
|
||||
@@ -3446,21 +3445,15 @@ func (b *LocalBackend) reconfigAppConnectorLocked(nm *netmap.NetworkMap, prefs i
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
domains []string
|
||||
routes []netip.Prefix
|
||||
)
|
||||
var domains []string
|
||||
for _, attr := range attrs {
|
||||
if slices.Contains(attr.Connectors, "*") || selfHasTag(attr.Connectors) {
|
||||
domains = append(domains, attr.Domains...)
|
||||
routes = append(routes, attr.Routes...)
|
||||
}
|
||||
}
|
||||
slices.Sort(domains)
|
||||
slices.SortFunc(routes, func(i, j netip.Prefix) int { return i.Addr().Compare(j.Addr()) })
|
||||
domains = slices.Compact(domains)
|
||||
routes = slices.Compact(routes)
|
||||
b.appConnector.UpdateDomainsAndRoutes(domains, routes)
|
||||
b.appConnector.UpdateDomains(domains)
|
||||
}
|
||||
|
||||
// authReconfig pushes a new configuration into wgengine, if engine
|
||||
@@ -3881,12 +3874,13 @@ func (b *LocalBackend) initPeerAPIListener() {
|
||||
ps := &peerAPIServer{
|
||||
b: b,
|
||||
taildrop: taildrop.ManagerOptions{
|
||||
Logf: b.logf,
|
||||
Clock: tstime.DefaultClock{Clock: b.clock},
|
||||
State: b.store,
|
||||
Dir: fileRoot,
|
||||
DirectFileMode: b.directFileRoot != "",
|
||||
SendFileNotify: b.sendFileNotify,
|
||||
Logf: b.logf,
|
||||
Clock: tstime.DefaultClock{Clock: b.clock},
|
||||
State: b.store,
|
||||
Dir: fileRoot,
|
||||
DirectFileMode: b.directFileRoot != "",
|
||||
AvoidFinalRename: !b.directFileDoFinalRename,
|
||||
SendFileNotify: b.sendFileNotify,
|
||||
}.New(),
|
||||
}
|
||||
if dm, ok := b.sys.DNSManager.GetOK(); ok {
|
||||
@@ -4125,11 +4119,7 @@ func (b *LocalBackend) applyPrefsToHostinfoLocked(hi *tailcfg.Hostinfo, prefs ip
|
||||
// TODO(bradfitz): this is called with b.mu held. Not ideal.
|
||||
// If the filesystem gets wedged or something we could block for
|
||||
// a long time. But probably fine.
|
||||
var err error
|
||||
sshHostKeys, err = b.getSSHHostKeyPublicStrings()
|
||||
if err != nil {
|
||||
b.logf("warning: unable to get SSH host keys, SSH will appear as disabled for this node: %v", err)
|
||||
}
|
||||
sshHostKeys = b.getSSHHostKeyPublicStrings()
|
||||
}
|
||||
hi.SSH_HostKeys = sshHostKeys
|
||||
|
||||
@@ -4187,9 +4177,6 @@ func (b *LocalBackend) enterStateLockedOnEntry(newState ipn.State) {
|
||||
switch newState {
|
||||
case ipn.NeedsLogin:
|
||||
systemd.Status("Needs login: %s", authURL)
|
||||
if b.seamlessRenewalEnabled() {
|
||||
break
|
||||
}
|
||||
b.blockEngineUpdates(true)
|
||||
fallthrough
|
||||
case ipn.Stopped:
|
||||
@@ -4560,7 +4547,6 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
|
||||
}
|
||||
|
||||
b.MagicConn().SetSilentDisco(b.ControlKnobs().SilentDisco.Load())
|
||||
b.MagicConn().SetProbeUDPLifetime(b.ControlKnobs().ProbeUDPLifetime.Load())
|
||||
|
||||
b.setDebugLogsByCapabilityLocked(nm)
|
||||
|
||||
@@ -5795,87 +5781,27 @@ var ErrDisallowedAutoRoute = errors.New("route is not allowed")
|
||||
// AdvertiseRoute implements the appc.RouteAdvertiser interface. It sets a new
|
||||
// route advertisement if one is not already present in the existing routes.
|
||||
// If the route is disallowed, ErrDisallowedAutoRoute is returned.
|
||||
func (b *LocalBackend) AdvertiseRoute(ipps ...netip.Prefix) error {
|
||||
finalRoutes := b.Prefs().AdvertiseRoutes().AsSlice()
|
||||
newRoutes := false
|
||||
|
||||
for _, ipp := range ipps {
|
||||
if !allowedAutoRoute(ipp) {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(finalRoutes, ipp) {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the new prefix is already contained by existing routes, skip it.
|
||||
if coveredRouteRange(finalRoutes, ipp) {
|
||||
continue
|
||||
}
|
||||
|
||||
finalRoutes = append(finalRoutes, ipp)
|
||||
newRoutes = true
|
||||
func (b *LocalBackend) AdvertiseRoute(ipp netip.Prefix) error {
|
||||
if !allowedAutoRoute(ipp) {
|
||||
return ErrDisallowedAutoRoute
|
||||
}
|
||||
|
||||
if !newRoutes {
|
||||
currentRoutes := b.Prefs().AdvertiseRoutes()
|
||||
if currentRoutes.ContainsFunc(func(r netip.Prefix) bool {
|
||||
// TODO(raggi): add support for subset checks and avoid subset route creations.
|
||||
return ipp.IsSingleIP() && r.Contains(ipp.Addr()) || r == ipp
|
||||
}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
routes := append(currentRoutes.AsSlice(), ipp)
|
||||
_, err := b.EditPrefs(&ipn.MaskedPrefs{
|
||||
Prefs: ipn.Prefs{
|
||||
AdvertiseRoutes: finalRoutes,
|
||||
AdvertiseRoutes: routes,
|
||||
},
|
||||
AdvertiseRoutesSet: true,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// coveredRouteRange checks if a route is already included in a slice of
|
||||
// prefixes.
|
||||
func coveredRouteRange(finalRoutes []netip.Prefix, ipp netip.Prefix) bool {
|
||||
for _, r := range finalRoutes {
|
||||
if ipp.IsSingleIP() {
|
||||
if r.Contains(ipp.Addr()) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if r.Contains(ipp.Addr()) && r.Contains(netipx.PrefixLastIP(ipp)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UnadvertiseRoute implements the appc.RouteAdvertiser interface. It removes
|
||||
// a route advertisement if one is present in the existing routes.
|
||||
func (b *LocalBackend) UnadvertiseRoute(toRemove ...netip.Prefix) error {
|
||||
currentRoutes := b.Prefs().AdvertiseRoutes().AsSlice()
|
||||
finalRoutes := currentRoutes[:0]
|
||||
|
||||
for _, ipp := range currentRoutes {
|
||||
if slices.Contains(toRemove, ipp) {
|
||||
continue
|
||||
}
|
||||
finalRoutes = append(finalRoutes, ipp)
|
||||
}
|
||||
|
||||
_, err := b.EditPrefs(&ipn.MaskedPrefs{
|
||||
Prefs: ipn.Prefs{
|
||||
AdvertiseRoutes: finalRoutes,
|
||||
},
|
||||
AdvertiseRoutesSet: true,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// seamlessRenewalEnabled reports whether seamless key renewals are enabled
|
||||
// (i.e. we saw our self node with the SeamlessKeyRenewal attr in a netmap).
|
||||
// This enables beta functionality of renewing node keys without breaking
|
||||
// connections.
|
||||
func (b *LocalBackend) seamlessRenewalEnabled() bool {
|
||||
return b.ControlKnobs().SeamlessKeyRenewal.Load()
|
||||
}
|
||||
|
||||
var (
|
||||
disallowedAddrs = []netip.Addr{
|
||||
netip.MustParseAddr("::1"),
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"go4.org/netipx"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"tailscale.com/appc"
|
||||
"tailscale.com/appc/appctest"
|
||||
"tailscale.com/control/controlclient"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/store/mem"
|
||||
@@ -1170,13 +1169,6 @@ func TestRouteAdvertiser(t *testing.T) {
|
||||
if routes.Len() != 1 || routes.At(0) != testPrefix {
|
||||
t.Fatalf("got routes %v, want %v", routes, []netip.Prefix{testPrefix})
|
||||
}
|
||||
|
||||
must.Do(ra.UnadvertiseRoute(testPrefix))
|
||||
|
||||
routes = b.Prefs().AdvertiseRoutes()
|
||||
if routes.Len() != 0 {
|
||||
t.Fatalf("got routes %v, want none", routes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterAdvertiserIgnoresContainedRoutes(t *testing.T) {
|
||||
@@ -1205,52 +1197,14 @@ func TestObserveDNSResponse(t *testing.T) {
|
||||
// ensure no error when no app connector is configured
|
||||
b.ObserveDNSResponse(dnsResponse("example.com.", "192.0.0.8"))
|
||||
|
||||
rc := &appctest.RouteCollector{}
|
||||
rc := &routeCollector{}
|
||||
b.appConnector = appc.NewAppConnector(t.Logf, rc)
|
||||
b.appConnector.UpdateDomains([]string{"example.com"})
|
||||
b.appConnector.Wait(context.Background())
|
||||
|
||||
b.ObserveDNSResponse(dnsResponse("example.com.", "192.0.0.8"))
|
||||
b.appConnector.Wait(context.Background())
|
||||
wantRoutes := []netip.Prefix{netip.MustParsePrefix("192.0.0.8/32")}
|
||||
if !slices.Equal(rc.Routes(), wantRoutes) {
|
||||
t.Fatalf("got routes %v, want %v", rc.Routes(), wantRoutes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoveredRouteRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
existingRoute netip.Prefix
|
||||
newRoute netip.Prefix
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
existingRoute: netip.MustParsePrefix("192.0.0.1/32"),
|
||||
newRoute: netip.MustParsePrefix("192.0.0.1/32"),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
existingRoute: netip.MustParsePrefix("192.0.0.1/32"),
|
||||
newRoute: netip.MustParsePrefix("192.0.0.2/32"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
existingRoute: netip.MustParsePrefix("192.0.0.0/24"),
|
||||
newRoute: netip.MustParsePrefix("192.0.0.1/32"),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
existingRoute: netip.MustParsePrefix("192.0.0.0/16"),
|
||||
newRoute: netip.MustParsePrefix("192.0.0.0/24"),
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := coveredRouteRange([]netip.Prefix{tt.existingRoute}, tt.newRoute)
|
||||
if got != tt.want {
|
||||
t.Errorf("coveredRouteRange(%v, %v) = %v, want %v", tt.existingRoute, tt.newRoute, got, tt.want)
|
||||
}
|
||||
if !slices.Equal(rc.routes, wantRoutes) {
|
||||
t.Fatalf("got routes %v, want %v", rc.routes, wantRoutes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1289,7 +1243,6 @@ func TestReconfigureAppConnector(t *testing.T) {
|
||||
}).View()
|
||||
|
||||
b.reconfigAppConnectorLocked(b.netMap, b.pm.prefs)
|
||||
b.appConnector.Wait(context.Background())
|
||||
|
||||
want := []string{"example.com"}
|
||||
if !slices.Equal(b.appConnector.Domains().AsSlice(), want) {
|
||||
@@ -1389,6 +1342,16 @@ func dnsResponse(domain, address string) []byte {
|
||||
return must.Get(b.Finish())
|
||||
}
|
||||
|
||||
// routeCollector is a test helper that collects the list of routes advertised
|
||||
type routeCollector struct {
|
||||
routes []netip.Prefix
|
||||
}
|
||||
|
||||
func (rc *routeCollector) AdvertiseRoute(pfx netip.Prefix) error {
|
||||
rc.routes = append(rc.routes, pfx)
|
||||
return nil
|
||||
}
|
||||
|
||||
type errorSyspolicyHandler struct {
|
||||
t *testing.T
|
||||
err error
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"go4.org/netipx"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"tailscale.com/appc"
|
||||
"tailscale.com/appc/appctest"
|
||||
"tailscale.com/client/tailscale/apitype"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/store/mem"
|
||||
@@ -686,11 +685,10 @@ func TestPeerAPIReplyToDNSQueries(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPeerAPIReplyToDNSQueriesAreObserved(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var h peerAPIHandler
|
||||
h.remoteAddr = netip.MustParseAddrPort("100.150.151.152:12345")
|
||||
|
||||
rc := &appctest.RouteCollector{}
|
||||
rc := &routeCollector{}
|
||||
eng, _ := wgengine.NewFakeUserspaceEngine(logger.Discard, 0)
|
||||
pm := must.Get(newProfileManager(new(mem.Store), t.Logf))
|
||||
h.ps = &peerAPIServer{
|
||||
@@ -702,7 +700,6 @@ func TestPeerAPIReplyToDNSQueriesAreObserved(t *testing.T) {
|
||||
},
|
||||
}
|
||||
h.ps.b.appConnector.UpdateDomains([]string{"example.com"})
|
||||
h.ps.b.appConnector.Wait(ctx)
|
||||
|
||||
h.ps.resolver = &fakeResolver{}
|
||||
f := filter.NewAllowAllForTest(logger.Discard)
|
||||
@@ -720,11 +717,10 @@ func TestPeerAPIReplyToDNSQueriesAreObserved(t *testing.T) {
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("unexpected status code: %v", w.Code)
|
||||
}
|
||||
h.ps.b.appConnector.Wait(ctx)
|
||||
|
||||
wantRoutes := []netip.Prefix{netip.MustParsePrefix("192.0.0.8/32")}
|
||||
if !slices.Equal(rc.Routes(), wantRoutes) {
|
||||
t.Errorf("got %v; want %v", rc.Routes(), wantRoutes)
|
||||
if !slices.Equal(rc.routes, wantRoutes) {
|
||||
t.Errorf("got %v; want %v", rc.routes, wantRoutes)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"slices"
|
||||
@@ -209,9 +210,11 @@ func init() {
|
||||
// is logged into so that we can keep track of things like their domain name
|
||||
// across user switches to disambiguate the same account but a different tailnet.
|
||||
func (pm *profileManager) SetPrefs(prefsIn ipn.PrefsView, np ipn.NetworkProfile) error {
|
||||
log.Printf("set prefs")
|
||||
prefs := prefsIn.AsStruct()
|
||||
newPersist := prefs.Persist
|
||||
if newPersist == nil || newPersist.NodeID == "" || newPersist.UserProfile.LoginName == "" {
|
||||
log.Printf("prefs: ignore profile")
|
||||
// We don't know anything about this profile, so ignore it for now.
|
||||
return pm.setPrefsLocked(prefs.View())
|
||||
}
|
||||
@@ -220,6 +223,7 @@ func (pm *profileManager) SetPrefs(prefsIn ipn.PrefsView, np ipn.NetworkProfile)
|
||||
up.DisplayName = up.LoginName
|
||||
}
|
||||
cp := pm.currentProfile
|
||||
log.Printf("current profile: %v", cp.UserProfile)
|
||||
// Check if we already have an existing profile that matches the user/node.
|
||||
if existing := pm.findMatchingProfiles(prefs); len(existing) > 0 {
|
||||
// We already have a profile for this user/node we should reuse it. Also
|
||||
@@ -256,12 +260,15 @@ func (pm *profileManager) SetPrefs(prefsIn ipn.PrefsView, np ipn.NetworkProfile)
|
||||
pm.knownProfiles[cp.ID] = cp
|
||||
pm.currentProfile = cp
|
||||
if err := pm.writeKnownProfiles(); err != nil {
|
||||
log.Printf("error writing known profiles")
|
||||
return err
|
||||
}
|
||||
if err := pm.setAsUserSelectedProfileLocked(); err != nil {
|
||||
log.Printf("error locking profile")
|
||||
return err
|
||||
}
|
||||
if err := pm.setPrefsLocked(prefs.View()); err != nil {
|
||||
log.Printf("error viewing prefs")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -34,7 +34,6 @@ import (
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/lazy"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/ctxkey"
|
||||
"tailscale.com/util/mak"
|
||||
"tailscale.com/version"
|
||||
)
|
||||
@@ -49,7 +48,8 @@ const (
|
||||
// current etag of a resource.
|
||||
var ErrETagMismatch = errors.New("etag mismatch")
|
||||
|
||||
var serveHTTPContextKey ctxkey.Key[*serveHTTPContext]
|
||||
// serveHTTPContextKey is the context.Value key for a *serveHTTPContext.
|
||||
type serveHTTPContextKey struct{}
|
||||
|
||||
type serveHTTPContext struct {
|
||||
SrcAddr netip.AddrPort
|
||||
@@ -433,7 +433,7 @@ func (b *LocalBackend) tcpHandlerForServe(dport uint16, srcAddr netip.AddrPort)
|
||||
hs := &http.Server{
|
||||
Handler: http.HandlerFunc(b.serveWebHandler),
|
||||
BaseContext: func(_ net.Listener) context.Context {
|
||||
return serveHTTPContextKey.WithValue(context.Background(), &serveHTTPContext{
|
||||
return context.WithValue(context.Background(), serveHTTPContextKey{}, &serveHTTPContext{
|
||||
SrcAddr: srcAddr,
|
||||
DestPort: dport,
|
||||
})
|
||||
@@ -500,6 +500,11 @@ func (b *LocalBackend) tcpHandlerForServe(dport uint16, srcAddr netip.AddrPort)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getServeHTTPContext(r *http.Request) (c *serveHTTPContext, ok bool) {
|
||||
c, ok = r.Context().Value(serveHTTPContextKey{}).(*serveHTTPContext)
|
||||
return c, ok
|
||||
}
|
||||
|
||||
func (b *LocalBackend) getServeHandler(r *http.Request) (_ ipn.HTTPHandlerView, at string, ok bool) {
|
||||
var z ipn.HTTPHandlerView // zero value
|
||||
|
||||
@@ -516,7 +521,7 @@ func (b *LocalBackend) getServeHandler(r *http.Request) (_ ipn.HTTPHandlerView,
|
||||
hostname = r.TLS.ServerName
|
||||
}
|
||||
|
||||
sctx, ok := serveHTTPContextKey.ValueOk(r.Context())
|
||||
sctx, ok := getServeHTTPContext(r)
|
||||
if !ok {
|
||||
b.logf("[unexpected] localbackend: no serveHTTPContext in request")
|
||||
return z, "", false
|
||||
@@ -605,20 +610,7 @@ func (rp *reverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
p := &httputil.ReverseProxy{Rewrite: func(r *httputil.ProxyRequest) {
|
||||
oldOutPath := r.Out.URL.Path
|
||||
r.SetURL(rp.url)
|
||||
|
||||
// If mount point matches the request path exactly, the outbound
|
||||
// request URL was set to empty string in serveWebHandler which
|
||||
// would have resulted in the outbound path set to <proxy path>
|
||||
// + '/' in SetURL. In that case, if the proxy path was set, we
|
||||
// want to send the request to the <proxy path> (without the
|
||||
// '/') .
|
||||
if oldOutPath == "" && rp.url.Path != "" {
|
||||
r.Out.URL.Path = rp.url.Path
|
||||
r.Out.URL.RawPath = rp.url.RawPath
|
||||
}
|
||||
|
||||
r.Out.Host = r.In.Host
|
||||
addProxyForwardedHeaders(r)
|
||||
rp.lb.addTailscaleIdentityHeaders(r)
|
||||
@@ -692,7 +684,7 @@ func addProxyForwardedHeaders(r *httputil.ProxyRequest) {
|
||||
if r.In.TLS != nil {
|
||||
r.Out.Header.Set("X-Forwarded-Proto", "https")
|
||||
}
|
||||
if c, ok := serveHTTPContextKey.ValueOk(r.Out.Context()); ok {
|
||||
if c, ok := getServeHTTPContext(r.Out); ok {
|
||||
r.Out.Header.Set("X-Forwarded-For", c.SrcAddr.Addr().String())
|
||||
}
|
||||
}
|
||||
@@ -704,7 +696,7 @@ func (b *LocalBackend) addTailscaleIdentityHeaders(r *httputil.ProxyRequest) {
|
||||
r.Out.Header.Del("Tailscale-User-Profile-Pic")
|
||||
r.Out.Header.Del("Tailscale-Headers-Info")
|
||||
|
||||
c, ok := serveHTTPContextKey.ValueOk(r.Out.Context())
|
||||
c, ok := getServeHTTPContext(r.Out)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ func TestGetServeHandler(t *testing.T) {
|
||||
TLS: &tls.ConnectionState{ServerName: serverName},
|
||||
}
|
||||
port := cmpx.Or(tt.port, 443)
|
||||
req = req.WithContext(serveHTTPContextKey.WithValue(req.Context(), &serveHTTPContext{
|
||||
req = req.WithContext(context.WithValue(req.Context(), serveHTTPContextKey{}, &serveHTTPContext{
|
||||
DestPort: port,
|
||||
}))
|
||||
|
||||
@@ -348,109 +348,7 @@ func TestServeConfigETag(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeHTTPProxyPath(t *testing.T) {
|
||||
b := newTestBackend(t)
|
||||
// Start test serve endpoint.
|
||||
testServ := httptest.NewServer(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
// Set the request URL path to a response header, so the
|
||||
// requested URL path can be checked in tests.
|
||||
t.Logf("adding path %s", r.URL.Path)
|
||||
w.Header().Add("Path", r.URL.Path)
|
||||
},
|
||||
))
|
||||
defer testServ.Close()
|
||||
tests := []struct {
|
||||
name string
|
||||
mountPoint string
|
||||
proxyPath string
|
||||
requestPath string
|
||||
wantRequestPath string
|
||||
}{
|
||||
{
|
||||
name: "/foo -> /foo, with mount point and path /foo",
|
||||
mountPoint: "/foo",
|
||||
proxyPath: "/foo",
|
||||
requestPath: "/foo",
|
||||
wantRequestPath: "/foo",
|
||||
},
|
||||
{
|
||||
name: "/foo/ -> /foo/, with mount point and path /foo",
|
||||
mountPoint: "/foo",
|
||||
proxyPath: "/foo",
|
||||
requestPath: "/foo/",
|
||||
wantRequestPath: "/foo/",
|
||||
},
|
||||
{
|
||||
name: "/foo -> /foo/, with mount point and path /foo/",
|
||||
mountPoint: "/foo/",
|
||||
proxyPath: "/foo/",
|
||||
requestPath: "/foo",
|
||||
wantRequestPath: "/foo/",
|
||||
},
|
||||
{
|
||||
name: "/-> /, with mount point and path /",
|
||||
mountPoint: "/",
|
||||
proxyPath: "/",
|
||||
requestPath: "/",
|
||||
wantRequestPath: "/",
|
||||
},
|
||||
{
|
||||
name: "/foo -> /foo, with mount point and path /",
|
||||
mountPoint: "/",
|
||||
proxyPath: "/",
|
||||
requestPath: "/foo",
|
||||
wantRequestPath: "/foo",
|
||||
},
|
||||
{
|
||||
name: "/foo/bar -> /foo/bar, with mount point and path /foo",
|
||||
mountPoint: "/foo",
|
||||
proxyPath: "/foo",
|
||||
requestPath: "/foo/bar",
|
||||
wantRequestPath: "/foo/bar",
|
||||
},
|
||||
{
|
||||
name: "/foo/bar/baz -> /foo/bar/baz, with mount point and path /foo",
|
||||
mountPoint: "/foo",
|
||||
proxyPath: "/foo",
|
||||
requestPath: "/foo/bar/baz",
|
||||
wantRequestPath: "/foo/bar/baz",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
conf := &ipn.ServeConfig{
|
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
||||
"example.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
|
||||
tt.mountPoint: {Proxy: testServ.URL + tt.proxyPath},
|
||||
}},
|
||||
},
|
||||
}
|
||||
if err := b.SetServeConfig(conf, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := &http.Request{
|
||||
URL: &url.URL{Path: tt.requestPath},
|
||||
TLS: &tls.ConnectionState{ServerName: "example.ts.net"},
|
||||
}
|
||||
req = req.WithContext(serveHTTPContextKey.WithValue(req.Context(),
|
||||
&serveHTTPContext{
|
||||
DestPort: 443,
|
||||
SrcAddr: netip.MustParseAddrPort("1.2.3.4:1234"), // random src
|
||||
}))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
b.serveWebHandler(w, req)
|
||||
|
||||
// Verify what path was requested
|
||||
p := w.Result().Header.Get("Path")
|
||||
if p != tt.wantRequestPath {
|
||||
t.Errorf("wanted request path %s got %s", tt.wantRequestPath, p)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestServeHTTPProxyHeaders(t *testing.T) {
|
||||
func TestServeHTTPProxy(t *testing.T) {
|
||||
b := newTestBackend(t)
|
||||
|
||||
// Start test serve endpoint.
|
||||
@@ -530,7 +428,7 @@ func TestServeHTTPProxyHeaders(t *testing.T) {
|
||||
URL: &url.URL{Path: "/"},
|
||||
TLS: &tls.ConnectionState{ServerName: "example.ts.net"},
|
||||
}
|
||||
req = req.WithContext(serveHTTPContextKey.WithValue(req.Context(), &serveHTTPContext{
|
||||
req = req.WithContext(context.WithValue(req.Context(), serveHTTPContextKey{}, &serveHTTPContext{
|
||||
DestPort: 443,
|
||||
SrcAddr: netip.MustParseAddrPort(tt.srcIP + ":1234"), // random src port for tests
|
||||
}))
|
||||
|
||||
@@ -210,16 +210,12 @@ func (b *LocalBackend) getSystemSSH_HostKeys() (ret map[string]ssh.Signer) {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (b *LocalBackend) getSSHHostKeyPublicStrings() ([]string, error) {
|
||||
signers, err := b.GetSSH_HostKeys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var keyStrings []string
|
||||
func (b *LocalBackend) getSSHHostKeyPublicStrings() (ret []string) {
|
||||
signers, _ := b.GetSSH_HostKeys()
|
||||
for _, signer := range signers {
|
||||
keyStrings = append(keyStrings, strings.TrimSpace(string(ssh.MarshalAuthorizedKey(signer.PublicKey()))))
|
||||
ret = append(ret, strings.TrimSpace(string(ssh.MarshalAuthorizedKey(signer.PublicKey()))))
|
||||
}
|
||||
return keyStrings, nil
|
||||
return ret
|
||||
}
|
||||
|
||||
// tailscaleSSHEnabled reports whether Tailscale SSH is currently enabled based
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
func (b *LocalBackend) getSSHHostKeyPublicStrings() ([]string, error) {
|
||||
return nil, nil
|
||||
func (b *LocalBackend) getSSHHostKeyPublicStrings() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *LocalBackend) getSSHUsernames(*tailcfg.C2NSSHUsernamesRequest) (*tailcfg.C2NSSHUsernamesResponse, error) {
|
||||
|
||||
@@ -251,12 +251,6 @@ func (s *Server) checkConnIdentityLocked(ci *ipnauth.ConnIdentity) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Always allow Windows SYSTEM user to connect,
|
||||
// even if Tailscale is currently being used by another user.
|
||||
if chkTok != nil && chkTok.IsLocalSystem() {
|
||||
return nil
|
||||
}
|
||||
|
||||
activeTok, err := active.WindowsToken()
|
||||
if err == nil {
|
||||
defer activeTok.Close()
|
||||
@@ -407,10 +401,8 @@ func (s *Server) addActiveHTTPRequest(req *http.Request, ci *ipnauth.ConnIdentit
|
||||
if !errors.Is(err, ipnauth.ErrNotImplemented) {
|
||||
s.logf("error obtaining access token: %v", err)
|
||||
}
|
||||
} else if !token.IsLocalSystem() {
|
||||
// Tell the LocalBackend about the identity we're now running as,
|
||||
// unless its the SYSTEM user. That user is not a real account and
|
||||
// doesn't have a home directory.
|
||||
} else {
|
||||
// Tell the LocalBackend about the identity we're now running as.
|
||||
uid, err := lb.SetCurrentUser(token)
|
||||
if err != nil {
|
||||
token.Close()
|
||||
|
||||
@@ -80,7 +80,6 @@ var handler = map[string]localAPIHandler{
|
||||
"component-debug-logging": (*Handler).serveComponentDebugLogging,
|
||||
"debug": (*Handler).serveDebug,
|
||||
"debug-derp-region": (*Handler).serveDebugDERPRegion,
|
||||
"debug-dial-types": (*Handler).serveDebugDialTypes,
|
||||
"debug-packet-filter-matches": (*Handler).serveDebugPacketFilterMatches,
|
||||
"debug-packet-filter-rules": (*Handler).serveDebugPacketFilterRules,
|
||||
"debug-portmap": (*Handler).serveDebugPortmap,
|
||||
@@ -841,76 +840,6 @@ func (h *Handler) serveComponentDebugLogging(w http.ResponseWriter, r *http.Requ
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func (h *Handler) serveDebugDialTypes(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "debug-dial-types access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if r.Method != httpm.POST {
|
||||
http.Error(w, "only POST allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
ip := r.FormValue("ip")
|
||||
port := r.FormValue("port")
|
||||
network := r.FormValue("network")
|
||||
|
||||
addr := ip + ":" + port
|
||||
if _, err := netip.ParseAddrPort(addr); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintf(w, "invalid address %q: %v", addr, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var bareDialer net.Dialer
|
||||
|
||||
dialer := h.b.Dialer()
|
||||
|
||||
var peerDialer net.Dialer
|
||||
peerDialer.Control = dialer.PeerDialControlFunc()
|
||||
|
||||
// Kick off a dial with each available dialer in parallel.
|
||||
dialers := []struct {
|
||||
name string
|
||||
dial func(context.Context, string, string) (net.Conn, error)
|
||||
}{
|
||||
{"SystemDial", dialer.SystemDial},
|
||||
{"UserDial", dialer.UserDial},
|
||||
{"PeerDial", peerDialer.DialContext},
|
||||
{"BareDial", bareDialer.DialContext},
|
||||
}
|
||||
type result struct {
|
||||
name string
|
||||
conn net.Conn
|
||||
err error
|
||||
}
|
||||
results := make(chan result, len(dialers))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, dialer := range dialers {
|
||||
dialer := dialer // loop capture
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
conn, err := dialer.dial(ctx, network, addr)
|
||||
results <- result{dialer.name, conn, err}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
for i := 0; i < len(dialers); i++ {
|
||||
res := <-results
|
||||
fmt.Fprintf(w, "[%s] connected=%v err=%v\n", res.name, res.conn != nil, res.err)
|
||||
if res.conn != nil {
|
||||
res.conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// servePprofFunc is the implementation of Handler.servePprof, after auth,
|
||||
// for platforms where we want to link it in.
|
||||
var servePprofFunc func(http.ResponseWriter, *http.Request)
|
||||
|
||||
12
ipn/prefs.go
12
ipn/prefs.go
@@ -26,7 +26,6 @@ import (
|
||||
"tailscale.com/types/preftype"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/util/syspolicy"
|
||||
)
|
||||
|
||||
// DefaultControlURL is the URL base of the control plane
|
||||
@@ -638,16 +637,11 @@ func (p PrefsView) ControlURLOrDefault() string {
|
||||
// If not configured, or if the configured value is a legacy name equivalent to
|
||||
// the default, then DefaultControlURL is returned instead.
|
||||
func (p *Prefs) ControlURLOrDefault() string {
|
||||
controlURL, err := syspolicy.GetString(syspolicy.ControlURL, p.ControlURL)
|
||||
if err != nil {
|
||||
controlURL = p.ControlURL
|
||||
}
|
||||
|
||||
if controlURL != "" {
|
||||
if controlURL != DefaultControlURL && IsLoginServerSynonym(controlURL) {
|
||||
if p.ControlURL != "" {
|
||||
if p.ControlURL != DefaultControlURL && IsLoginServerSynonym(p.ControlURL) {
|
||||
return DefaultControlURL
|
||||
}
|
||||
return controlURL
|
||||
return p.ControlURL
|
||||
}
|
||||
return DefaultControlURL
|
||||
}
|
||||
|
||||
@@ -47,14 +47,14 @@ type ConnectorList struct {
|
||||
}
|
||||
|
||||
// ConnectorSpec describes a Tailscale node to be deployed in the cluster.
|
||||
// +kubebuilder:validation:XValidation:rule="has(self.subnetRouter) || self.exitNode == true",message="A Connector needs to be either an exit node or a subnet router, or both."
|
||||
// +kubebuilder:validation:XValidation:rule="has(self.subnetRouter) || self.isExitNode == true",message="A Connector needs to be either an exit node or a subnet router, or both."
|
||||
type ConnectorSpec struct {
|
||||
// Tags that the Tailscale node will be tagged with.
|
||||
// Defaults to [tag:k8s].
|
||||
// To autoapprove the subnet routes or exit node defined by a Connector,
|
||||
// you can configure Tailscale ACLs to give these tags the necessary
|
||||
// permissions.
|
||||
// See https://tailscale.com/kb/1018/acls/#auto-approvers-for-routes-and-exit-nodes.
|
||||
// See https://tailscale.com/kb/1018/acls/#auto-approvers-for-routes-and-exit-nodes
|
||||
// If you specify custom tags here, you must also make the operator an owner of these tags.
|
||||
// See https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator.
|
||||
// Tags cannot be changed once a Connector node has been created.
|
||||
@@ -62,7 +62,7 @@ type ConnectorSpec struct {
|
||||
// +optional
|
||||
Tags Tags `json:"tags,omitempty"`
|
||||
// Hostname is the tailnet hostname that should be assigned to the
|
||||
// Connector node. If unset, hostname defaults to <connector
|
||||
// Connector node. If unset, hostname is defaulted to <connector
|
||||
// name>-connector. Hostname can contain lower case letters, numbers and
|
||||
// dashes, it must not start or end with a dash and must be between 2
|
||||
// and 63 characters long.
|
||||
@@ -73,21 +73,21 @@ type ConnectorSpec struct {
|
||||
// https://tailscale.com/kb/1019/subnets/
|
||||
// +optional
|
||||
SubnetRouter *SubnetRouter `json:"subnetRouter"`
|
||||
// ExitNode defines whether the Connector node should act as a
|
||||
// IsExitNode defines whether the Connector node should act as a
|
||||
// Tailscale exit node. Defaults to false.
|
||||
// https://tailscale.com/kb/1103/exit-nodes
|
||||
// +optional
|
||||
ExitNode bool `json:"exitNode"`
|
||||
IsExitNode bool `json:"isExitNode"`
|
||||
}
|
||||
|
||||
// SubnetRouter defines subnet routes that should be exposed to tailnet via a
|
||||
// Connector node.
|
||||
type SubnetRouter struct {
|
||||
// AdvertiseRoutes refer to CIDRs that the subnet router should make
|
||||
// Routes refer to in-cluster CIDRs that the subnet router should make
|
||||
// available. Route values must be strings that represent a valid IPv4
|
||||
// or IPv6 CIDR range. Values can be Tailscale 4via6 subnet routes.
|
||||
// https://tailscale.com/kb/1201/4via6-subnets/
|
||||
AdvertiseRoutes Routes `json:"advertiseRoutes"`
|
||||
Routes Routes `json:"routes"`
|
||||
}
|
||||
|
||||
type Tags []Tag
|
||||
@@ -100,13 +100,9 @@ func (tags Tags) Stringify() []string {
|
||||
return stringTags
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:MinItems=1
|
||||
type Routes []Route
|
||||
|
||||
func (routes Routes) Stringify() string {
|
||||
if len(routes) < 1 {
|
||||
return ""
|
||||
}
|
||||
var sb strings.Builder
|
||||
sb.WriteString(string(routes[0]))
|
||||
for _, r := range routes[1:] {
|
||||
|
||||
@@ -158,8 +158,8 @@ func (in Routes) DeepCopy() Routes {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SubnetRouter) DeepCopyInto(out *SubnetRouter) {
|
||||
*out = *in
|
||||
if in.AdvertiseRoutes != nil {
|
||||
in, out := &in.AdvertiseRoutes, &out.AdvertiseRoutes
|
||||
if in.Routes != nil {
|
||||
in, out := &in.Routes, &out.Routes
|
||||
*out = make(Routes, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
|
||||
@@ -50,9 +50,9 @@ Client][]. See also the dependencies in the [Tailscale CLI][].
|
||||
- [github.com/jmespath/go-jmespath](https://pkg.go.dev/github.com/jmespath/go-jmespath) ([Apache-2.0](https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE))
|
||||
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/5c7d0dd6ab86/license))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.3.5/LICENSE.md))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.17.4/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.17.4/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.17.4/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.17.0/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.17.0/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.17.0/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE))
|
||||
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.3.2/LICENSE.md))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.2/LICENSE.md))
|
||||
@@ -64,12 +64,12 @@ Client][]. See also the dependencies in the [Tailscale CLI][].
|
||||
- [github.com/pkg/errors](https://pkg.go.dev/github.com/pkg/errors) ([BSD-2-Clause](https://github.com/pkg/errors/blob/v0.9.1/LICENSE))
|
||||
- [github.com/safchain/ethtool](https://pkg.go.dev/github.com/safchain/ethtool) ([Apache-2.0](https://github.com/safchain/ethtool/blob/v0.3.0/LICENSE))
|
||||
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
|
||||
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/7ce1f622c780/LICENSE))
|
||||
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/f0b76a10a08e/LICENSE))
|
||||
- [github.com/tailscale/goupnp](https://pkg.go.dev/github.com/tailscale/goupnp) ([BSD-2-Clause](https://github.com/tailscale/goupnp/blob/c64d0f06ea05/LICENSE))
|
||||
- [github.com/tailscale/hujson](https://pkg.go.dev/github.com/tailscale/hujson) ([BSD-3-Clause](https://github.com/tailscale/hujson/blob/20486734a56a/LICENSE))
|
||||
- [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/cabfb018fe85/LICENSE))
|
||||
- [github.com/tailscale/tailscale-android](https://pkg.go.dev/github.com/tailscale/tailscale-android) ([BSD-3-Clause](https://github.com/tailscale/tailscale-android/blob/HEAD/LICENSE))
|
||||
- [github.com/tailscale/web-client-prebuilt](https://pkg.go.dev/github.com/tailscale/web-client-prebuilt) ([BSD-3-Clause](https://github.com/tailscale/web-client-prebuilt/blob/26bf65339dda/LICENSE))
|
||||
- [github.com/tailscale/web-client-prebuilt](https://pkg.go.dev/github.com/tailscale/web-client-prebuilt) ([BSD-3-Clause](https://github.com/tailscale/web-client-prebuilt/blob/9b3142ca6f79/LICENSE))
|
||||
- [github.com/tailscale/wireguard-go](https://pkg.go.dev/github.com/tailscale/wireguard-go) ([MIT](https://github.com/tailscale/wireguard-go/blob/cc193a0b3272/LICENSE))
|
||||
- [github.com/tcnksm/go-httpstat](https://pkg.go.dev/github.com/tcnksm/go-httpstat) ([MIT](https://github.com/tcnksm/go-httpstat/blob/v0.2.0/LICENSE))
|
||||
- [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/3e8cd9d6bf63/LICENSE))
|
||||
@@ -80,14 +80,14 @@ Client][]. See also the dependencies in the [Tailscale CLI][].
|
||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/6213f710f925/LICENSE))
|
||||
- [go4.org/unsafe/assume-no-moving-gc](https://pkg.go.dev/go4.org/unsafe/assume-no-moving-gc) ([BSD-3-Clause](https://github.com/go4org/unsafe-assume-no-moving-gc/blob/e7c30c78aeb2/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/08396bb9:LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.15.0:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/92128663:LICENSE))
|
||||
- [golang.org/x/exp/shiny](https://pkg.go.dev/golang.org/x/exp/shiny) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/334a2380:shiny/LICENSE))
|
||||
- [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.12.0:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.18.0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.5.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.15.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.15.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.14.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.14.0:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/4fe30062272c/LICENSE))
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Tailscale for macOS/iOS/tvOS dependencies
|
||||
# Tailscale for macOS/iOS dependencies
|
||||
|
||||
The following open source dependencies are used to build Tailscale on [macOS][], [iOS][] and [tvOS][]. See also the dependencies in the [Tailscale CLI][].
|
||||
The following open source dependencies are used to build Tailscale on [macOS][]
|
||||
and [iOS][]. See also the dependencies in the [Tailscale CLI][].
|
||||
|
||||
[macOS]: https://tailscale.com/kb/1016/install-mac/
|
||||
[iOS]: https://tailscale.com/kb/1020/install-ios/
|
||||
[tvOS]: https://tailscale.com/kb/1280/appletv/
|
||||
[Tailscale CLI]: ./tailscale.md
|
||||
|
||||
## Go Packages
|
||||
@@ -41,9 +41,9 @@ The following open source dependencies are used to build Tailscale on [macOS][],
|
||||
- [github.com/jmespath/go-jmespath](https://pkg.go.dev/github.com/jmespath/go-jmespath) ([Apache-2.0](https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE))
|
||||
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/5c7d0dd6ab86/license))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.3.5/LICENSE.md))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.17.4/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.17.4/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.17.4/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.17.0/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.17.0/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.17.0/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE))
|
||||
- [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.3.2/LICENSE.md))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.2/LICENSE.md))
|
||||
@@ -53,7 +53,7 @@ The following open source dependencies are used to build Tailscale on [macOS][],
|
||||
- [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md))
|
||||
- [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.18/LICENSE))
|
||||
- [github.com/safchain/ethtool](https://pkg.go.dev/github.com/safchain/ethtool) ([Apache-2.0](https://github.com/safchain/ethtool/blob/v0.3.0/LICENSE))
|
||||
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/7ce1f622c780/LICENSE))
|
||||
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/f0b76a10a08e/LICENSE))
|
||||
- [github.com/tailscale/goupnp](https://pkg.go.dev/github.com/tailscale/goupnp) ([BSD-2-Clause](https://github.com/tailscale/goupnp/blob/c64d0f06ea05/LICENSE))
|
||||
- [github.com/tailscale/hujson](https://pkg.go.dev/github.com/tailscale/hujson) ([BSD-3-Clause](https://github.com/tailscale/hujson/blob/20486734a56a/LICENSE))
|
||||
- [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/cabfb018fe85/LICENSE))
|
||||
@@ -65,12 +65,12 @@ The following open source dependencies are used to build Tailscale on [macOS][],
|
||||
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/6213f710f925/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/08396bb9:LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.15.0:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/92128663:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.18.0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.5.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.15.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.15.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.14.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.14.0:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
|
||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/4fe30062272c/LICENSE))
|
||||
|
||||
@@ -53,9 +53,9 @@ Some packages may only be included on certain architectures or operating systems
|
||||
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/5c7d0dd6ab86/license))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.3.5/LICENSE.md))
|
||||
- [github.com/kballard/go-shellquote](https://pkg.go.dev/github.com/kballard/go-shellquote) ([MIT](https://github.com/kballard/go-shellquote/blob/95032a82bc51/LICENSE))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.17.4/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.17.4/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.17.4/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.17.0/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.17.0/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.17.0/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE))
|
||||
- [github.com/kr/fs](https://pkg.go.dev/github.com/kr/fs) ([BSD-3-Clause](https://github.com/kr/fs/blob/v0.1.0/LICENSE))
|
||||
- [github.com/mattn/go-colorable](https://pkg.go.dev/github.com/mattn/go-colorable) ([MIT](https://github.com/mattn/go-colorable/blob/v0.1.13/LICENSE))
|
||||
@@ -74,10 +74,10 @@ Some packages may only be included on certain architectures or operating systems
|
||||
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
|
||||
- [github.com/tailscale/certstore](https://pkg.go.dev/github.com/tailscale/certstore) ([MIT](https://github.com/tailscale/certstore/blob/d3fa0460f47e/LICENSE.md))
|
||||
- [github.com/tailscale/go-winio](https://pkg.go.dev/github.com/tailscale/go-winio) ([MIT](https://github.com/tailscale/go-winio/blob/c4f33415bf55/LICENSE))
|
||||
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/7ce1f622c780/LICENSE))
|
||||
- [github.com/tailscale/golang-x-crypto](https://pkg.go.dev/github.com/tailscale/golang-x-crypto) ([BSD-3-Clause](https://github.com/tailscale/golang-x-crypto/blob/f0b76a10a08e/LICENSE))
|
||||
- [github.com/tailscale/hujson](https://pkg.go.dev/github.com/tailscale/hujson) ([BSD-3-Clause](https://github.com/tailscale/hujson/blob/20486734a56a/LICENSE))
|
||||
- [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/cabfb018fe85/LICENSE))
|
||||
- [github.com/tailscale/web-client-prebuilt](https://pkg.go.dev/github.com/tailscale/web-client-prebuilt) ([BSD-3-Clause](https://github.com/tailscale/web-client-prebuilt/blob/5ca22df9e6e7/LICENSE))
|
||||
- [github.com/tailscale/web-client-prebuilt](https://pkg.go.dev/github.com/tailscale/web-client-prebuilt) ([BSD-3-Clause](https://github.com/tailscale/web-client-prebuilt/blob/3a45625fe806/LICENSE))
|
||||
- [github.com/tailscale/wireguard-go](https://pkg.go.dev/github.com/tailscale/wireguard-go) ([MIT](https://github.com/tailscale/wireguard-go/blob/cc193a0b3272/LICENSE))
|
||||
- [github.com/tcnksm/go-httpstat](https://pkg.go.dev/github.com/tcnksm/go-httpstat) ([MIT](https://github.com/tcnksm/go-httpstat/blob/v0.2.0/LICENSE))
|
||||
- [github.com/toqueteos/webbrowser](https://pkg.go.dev/github.com/toqueteos/webbrowser) ([MIT](https://github.com/toqueteos/webbrowser/blob/v1.2.0/LICENSE.md))
|
||||
@@ -88,13 +88,13 @@ Some packages may only be included on certain architectures or operating systems
|
||||
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/6213f710f925/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/08396bb9:LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.15.0:LICENSE))
|
||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/92128663:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.18.0:LICENSE))
|
||||
- [golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2) ([BSD-3-Clause](https://cs.opensource.google/go/x/oauth2/+/v0.12.0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.5.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.15.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.15.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.14.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.14.0:LICENSE))
|
||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
|
||||
- [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2))
|
||||
|
||||
@@ -13,23 +13,8 @@ Windows][]. See also the dependencies in the [Tailscale CLI][].
|
||||
- [github.com/alexbrainman/sspi](https://pkg.go.dev/github.com/alexbrainman/sspi) ([BSD-3-Clause](https://github.com/alexbrainman/sspi/blob/1a75b4708caa/LICENSE))
|
||||
- [github.com/apenwarr/fixconsole](https://pkg.go.dev/github.com/apenwarr/fixconsole) ([Apache-2.0](https://github.com/apenwarr/fixconsole/blob/5a9f6489cc29/LICENSE))
|
||||
- [github.com/apenwarr/w32](https://pkg.go.dev/github.com/apenwarr/w32) ([BSD-3-Clause](https://github.com/apenwarr/w32/blob/aa00fece76ab/LICENSE))
|
||||
- [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.21.0/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.18.42/config/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.13.40/credentials/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.13.11/feature/ec2/imds/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.1.41/internal/configsources/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.4.35/internal/endpoints/v2/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.3.43/internal/ini/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.21.0/internal/sync/singleflight/LICENSE))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.9.35/service/internal/presigned-url/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.38.0/service/ssm/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.14.1/service/sso/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.17.1/service/ssooidc/LICENSE.txt))
|
||||
- [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.22.0/service/sts/LICENSE.txt))
|
||||
- [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.14.2/LICENSE))
|
||||
- [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.14.2/internal/sync/singleflight/LICENSE))
|
||||
- [github.com/coreos/go-iptables/iptables](https://pkg.go.dev/github.com/coreos/go-iptables/iptables) ([Apache-2.0](https://github.com/coreos/go-iptables/blob/v0.7.0/LICENSE))
|
||||
- [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/65927751e9eb/LICENSE))
|
||||
- [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/e994401fc077/LICENSE))
|
||||
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.5.0/LICENSE))
|
||||
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
|
||||
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE))
|
||||
@@ -37,12 +22,11 @@ Windows][]. See also the dependencies in the [Tailscale CLI][].
|
||||
- [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.3.1/LICENSE))
|
||||
- [github.com/gregjones/httpcache](https://pkg.go.dev/github.com/gregjones/httpcache) ([MIT](https://github.com/gregjones/httpcache/blob/901d90724c79/LICENSE.txt))
|
||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.1.0/LICENSE))
|
||||
- [github.com/jmespath/go-jmespath](https://pkg.go.dev/github.com/jmespath/go-jmespath) ([Apache-2.0](https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE))
|
||||
- [github.com/josharian/native](https://pkg.go.dev/github.com/josharian/native) ([MIT](https://github.com/josharian/native/blob/5c7d0dd6ab86/license))
|
||||
- [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.3.5/LICENSE.md))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.17.4/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.17.4/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.17.4/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.17.0/LICENSE))
|
||||
- [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.17.0/internal/snapref/LICENSE))
|
||||
- [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.17.0/zstd/internal/xxhash/LICENSE.txt))
|
||||
- [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/v1.7.2/LICENSE.md))
|
||||
- [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.5.0/LICENSE.md))
|
||||
- [github.com/miekg/dns](https://pkg.go.dev/github.com/miekg/dns) ([BSD-3-Clause](https://github.com/miekg/dns/blob/v1.1.56/LICENSE))
|
||||
@@ -52,22 +36,22 @@ Windows][]. See also the dependencies in the [Tailscale CLI][].
|
||||
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
|
||||
- [github.com/tailscale/go-winio](https://pkg.go.dev/github.com/tailscale/go-winio) ([MIT](https://github.com/tailscale/go-winio/blob/c4f33415bf55/LICENSE))
|
||||
- [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/cabfb018fe85/LICENSE))
|
||||
- [github.com/tailscale/walk](https://pkg.go.dev/github.com/tailscale/walk) ([BSD-3-Clause](https://github.com/tailscale/walk/blob/6a278000867c/LICENSE))
|
||||
- [github.com/tailscale/win](https://pkg.go.dev/github.com/tailscale/win) ([BSD-3-Clause](https://github.com/tailscale/win/blob/d2e5cdeed6dc/LICENSE))
|
||||
- [github.com/tailscale/walk](https://pkg.go.dev/github.com/tailscale/walk) ([BSD-3-Clause](https://github.com/tailscale/walk/blob/dff4ed649e49/LICENSE))
|
||||
- [github.com/tailscale/win](https://pkg.go.dev/github.com/tailscale/win) ([BSD-3-Clause](https://github.com/tailscale/win/blob/84569fd814a9/LICENSE))
|
||||
- [github.com/tc-hib/winres](https://pkg.go.dev/github.com/tc-hib/winres) ([0BSD](https://github.com/tc-hib/winres/blob/v0.2.1/LICENSE))
|
||||
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/v1.2.1-beta.2/LICENSE))
|
||||
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/v0.0.4/LICENSE))
|
||||
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/6213f710f925/LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/08396bb9:LICENSE))
|
||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.15.0:LICENSE))
|
||||
- [golang.org/x/exp/constraints](https://pkg.go.dev/golang.org/x/exp/constraints) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/92128663:LICENSE))
|
||||
- [golang.org/x/image/bmp](https://pkg.go.dev/golang.org/x/image/bmp) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.14.0:LICENSE))
|
||||
- [golang.org/x/image/bmp](https://pkg.go.dev/golang.org/x/image/bmp) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.12.0:LICENSE))
|
||||
- [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.14.0:LICENSE))
|
||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.18.0:LICENSE))
|
||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.5.0:LICENSE))
|
||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.15.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.15.0:LICENSE))
|
||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.14.0:LICENSE))
|
||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.14.0:LICENSE))
|
||||
- [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2))
|
||||
- [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.5.3))
|
||||
|
||||
@@ -460,10 +460,6 @@ func (f *forwarder) sendDoH(ctx context.Context, urlBase string, c *http.Client,
|
||||
var (
|
||||
verboseDNSForward = envknob.RegisterBool("TS_DEBUG_DNS_FORWARD_SEND")
|
||||
skipTCPRetry = envknob.RegisterBool("TS_DNS_FORWARD_SKIP_TCP_RETRY")
|
||||
|
||||
// For correlating log messages in the send() function; only used when
|
||||
// verboseDNSForward() is true.
|
||||
forwarderCount atomic.Uint64
|
||||
)
|
||||
|
||||
// send sends packet to dst. It is best effort.
|
||||
@@ -471,10 +467,9 @@ var (
|
||||
// send expects the reply to have the same txid as txidOut.
|
||||
func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDelay) (ret []byte, err error) {
|
||||
if verboseDNSForward() {
|
||||
id := forwarderCount.Add(1)
|
||||
f.logf("forwarder.send(%q) [%d] ...", rr.name.Addr, id)
|
||||
f.logf("forwarder.send(%q) ...", rr.name.Addr)
|
||||
defer func() {
|
||||
f.logf("forwarder.send(%q) [%d] = %v, %v", rr.name.Addr, id, len(ret), err)
|
||||
f.logf("forwarder.send(%q) = %v, %v", rr.name.Addr, len(ret), err)
|
||||
}()
|
||||
}
|
||||
if strings.HasPrefix(rr.name.Addr, "http://") {
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Common code for FreeBSD and Darwin. This might also work on other
|
||||
// BSD systems (e.g. OpenBSD) but has not been tested.
|
||||
// Not used on iOS. See defaultroute_ios.go.
|
||||
|
||||
//go:build !ios && (darwin || freebsd)
|
||||
|
||||
package interfaces
|
||||
|
||||
import "net"
|
||||
|
||||
func defaultRoute() (d DefaultRouteDetails, err error) {
|
||||
idx, err := DefaultRouteInterfaceIndex()
|
||||
if err != nil {
|
||||
return d, err
|
||||
}
|
||||
iface, err := net.InterfaceByIndex(idx)
|
||||
if err != nil {
|
||||
return d, err
|
||||
}
|
||||
d.InterfaceName = iface.Name
|
||||
d.InterfaceIndex = idx
|
||||
return d, nil
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build ios
|
||||
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"tailscale.com/syncs"
|
||||
)
|
||||
|
||||
var (
|
||||
lastKnownDefaultRouteIfName syncs.AtomicValue[string]
|
||||
)
|
||||
|
||||
// UpdateLastKnownDefaultRouteInterface is called by ipn-go-bridge in the iOS app when
|
||||
// our NWPathMonitor instance detects a network path transition.
|
||||
func UpdateLastKnownDefaultRouteInterface(ifName string) {
|
||||
if ifName == "" {
|
||||
return
|
||||
}
|
||||
lastKnownDefaultRouteIfName.Store(ifName)
|
||||
log.Printf("defaultroute_ios: update from Swift, ifName = %s", ifName)
|
||||
}
|
||||
|
||||
func defaultRoute() (d DefaultRouteDetails, err error) {
|
||||
// We cannot rely on the delegated interface data on iOS. The NetworkExtension framework
|
||||
// seems to set the delegate interface only once, upon the *creation* of the VPN tunnel.
|
||||
// If a network transition (e.g. from Wi-Fi to Cellular) happens while the tunnel is
|
||||
// connected, it will be ignored and we will still try to set Wi-Fi as the default route
|
||||
// because the delegated interface is not updated by the NetworkExtension framework.
|
||||
//
|
||||
// We work around this on the Swift side with a NWPathMonitor instance that observes
|
||||
// the interface name of the first currently satisfied network path. Our Swift code will
|
||||
// call into `UpdateLastKnownDefaultRouteInterface`, so we can rely on that when it is set.
|
||||
//
|
||||
// If for any reason the Swift machinery didn't work and we don't get any updates, here
|
||||
// we also have some fallback logic: we try finding a hardcoded Wi-Fi interface called en0.
|
||||
// If en0 is down, we fall back to cellular (pdp_ip0) as a last resort. This doesn't handle
|
||||
// all edge cases like USB-Ethernet adapters or multiple Ethernet interfaces, but is good
|
||||
// enough to ensure connectivity isn't broken.
|
||||
|
||||
// Start by getting all available interfaces.
|
||||
interfaces, err := netInterfaces()
|
||||
if err != nil {
|
||||
log.Printf("defaultroute_ios: could not get interfaces: %v", err)
|
||||
return d, ErrNoGatewayIndexFound
|
||||
}
|
||||
|
||||
getInterfaceByName := func(name string) *Interface {
|
||||
for _, ifc := range interfaces {
|
||||
if ifc.Name != name {
|
||||
continue
|
||||
}
|
||||
|
||||
if !ifc.IsUp() {
|
||||
log.Println("defaultroute_ios: %s is down", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
addrs, _ := ifc.Addrs()
|
||||
if len(addrs) == 0 {
|
||||
log.Println("defaultroute_ios: %s has no addresses", name)
|
||||
return nil
|
||||
}
|
||||
return &ifc
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Did Swift set lastKnownDefaultRouteInterface? If so, we should use it and don't bother
|
||||
// with anything else. However, for sanity, do check whether Swift gave us with an interface
|
||||
// that exists, is up, and has an address.
|
||||
if swiftIfName := lastKnownDefaultRouteIfName.Load(); swiftIfName != "" {
|
||||
ifc := getInterfaceByName(swiftIfName)
|
||||
if ifc != nil {
|
||||
log.Printf("defaultroute_ios: using %s (provided by Swift)", ifc.Name)
|
||||
d.InterfaceName = ifc.Name
|
||||
d.InterfaceIndex = ifc.Index
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Start of our fallback logic if Swift didn't give us an interface name, or gave us an invalid
|
||||
// one.
|
||||
// We start by attempting to use the Wi-Fi interface, which on iPhone is always called en0.
|
||||
enZeroIf := getInterfaceByName("en0")
|
||||
if enZeroIf != nil {
|
||||
log.Println("defaultroute_ios: using en0 (fallback)")
|
||||
d.InterfaceName = enZeroIf.Name
|
||||
d.InterfaceIndex = enZeroIf.Index
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Did it not work? Let's try with Cellular (pdp_ip0).
|
||||
cellIf := getInterfaceByName("pdp_ip0")
|
||||
if cellIf != nil {
|
||||
log.Println("defaultroute_ios: using pdp_ip0 (fallback)")
|
||||
d.InterfaceName = cellIf.Name
|
||||
d.InterfaceIndex = cellIf.Index
|
||||
return d, nil
|
||||
}
|
||||
|
||||
log.Println("defaultroute_ios: no running interfaces available")
|
||||
return d, ErrNoGatewayIndexFound
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
|
||||
@@ -20,6 +21,20 @@ import (
|
||||
"tailscale.com/net/netaddr"
|
||||
)
|
||||
|
||||
func defaultRoute() (d DefaultRouteDetails, err error) {
|
||||
idx, err := DefaultRouteInterfaceIndex()
|
||||
if err != nil {
|
||||
return d, err
|
||||
}
|
||||
iface, err := net.InterfaceByIndex(idx)
|
||||
if err != nil {
|
||||
return d, err
|
||||
}
|
||||
d.InterfaceName = iface.Name
|
||||
d.InterfaceIndex = idx
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// ErrNoGatewayIndexFound is returned by DefaultRouteInterfaceIndex when no
|
||||
// default route is found.
|
||||
var ErrNoGatewayIndexFound = errors.New("no gateway index found")
|
||||
|
||||
@@ -478,28 +478,6 @@ func (m *Monitor) IsMajorChangeFrom(s1, s2 *interfaces.State) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// Iterate over s2 in case there is a field in s2 that doesn't exist in s1
|
||||
for iname, i := range s2.Interface {
|
||||
if iname == m.tsIfName {
|
||||
// Ignore changes in the Tailscale interface itself.
|
||||
continue
|
||||
}
|
||||
ips := s2.InterfaceIPs[iname]
|
||||
if !m.isInterestingInterface(i, ips) {
|
||||
continue
|
||||
}
|
||||
i1, ok := s1.Interface[iname]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
ips1, ok := s1.InterfaceIPs[iname]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if !i.Equal(i1) || !prefixesMajorEqual(ips, ips1) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -29,12 +29,11 @@ func validateViaPrefix(ipp netip.Prefix) error {
|
||||
// The first 64 bits of a are the via prefix.
|
||||
// The next 32 bits are the "site ID".
|
||||
// The last 32 bits are the IPv4.
|
||||
//
|
||||
// We used to only allow advertising site IDs from 0-255, but we have
|
||||
// since relaxed this (as of 2024-01) to allow IDs from 0-65535.
|
||||
// For now, we reserve the top 3 bytes of the site ID,
|
||||
// and only allow users to use site IDs 0-255.
|
||||
siteID := binary.BigEndian.Uint32(a[8:12])
|
||||
if siteID > 0xFFFF {
|
||||
return fmt.Errorf("route %v contains invalid site ID %08x; must be 0xffff or less", ipp, siteID)
|
||||
if siteID > 0xFF {
|
||||
return fmt.Errorf("route %v contains invalid site ID %08x; must be 0xff or less", ipp, siteID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,303 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !js
|
||||
|
||||
// (no raw sockets in JS/WASM)
|
||||
|
||||
package portmapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tailscale/goupnp"
|
||||
"github.com/tailscale/goupnp/soap"
|
||||
)
|
||||
|
||||
const (
|
||||
urn_LegacyWANPPPConnection_1 = "urn:dslforum-org:service:WANPPPConnection:1"
|
||||
urn_LegacyWANIPConnection_1 = "urn:dslforum-org:service:WANIPConnection:1"
|
||||
)
|
||||
|
||||
// legacyWANPPPConnection1 is the same as internetgateway2.WANPPPConnection1,
|
||||
// except using the old URN that starts with "urn:dslforum-org".
|
||||
//
|
||||
// The definition for this can be found in older documentation about UPnP; for
|
||||
// the purposes of this implementation, we're referring to "DSL Forum TR-064:
|
||||
// LAN-Side DSL CPE Configuration", which, while deprecated, can be found at:
|
||||
//
|
||||
// https://www.broadband-forum.org/wp-content/uploads/2018/11/TR-064_Corrigendum-1.pdf
|
||||
// https://www.broadband-forum.org/pdfs/tr-064-1-0-1.pdf
|
||||
type legacyWANPPPConnection1 struct {
|
||||
goupnp.ServiceClient
|
||||
}
|
||||
|
||||
// AddPortMapping implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) AddPortMapping(
|
||||
ctx context.Context,
|
||||
NewRemoteHost string,
|
||||
NewExternalPort uint16,
|
||||
NewProtocol string,
|
||||
NewInternalPort uint16,
|
||||
NewInternalClient string,
|
||||
NewEnabled bool,
|
||||
NewPortMappingDescription string,
|
||||
NewLeaseDuration uint32,
|
||||
) (err error) {
|
||||
// Request structure.
|
||||
request := &struct {
|
||||
NewRemoteHost string
|
||||
NewExternalPort string
|
||||
NewProtocol string
|
||||
NewInternalPort string
|
||||
NewInternalClient string
|
||||
NewEnabled string
|
||||
NewPortMappingDescription string
|
||||
NewLeaseDuration string
|
||||
}{}
|
||||
|
||||
if request.NewRemoteHost, err = soap.MarshalString(NewRemoteHost); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewExternalPort, err = soap.MarshalUi2(NewExternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewProtocol, err = soap.MarshalString(NewProtocol); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewInternalPort, err = soap.MarshalUi2(NewInternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewInternalClient, err = soap.MarshalString(NewInternalClient); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewEnabled, err = soap.MarshalBoolean(NewEnabled); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewPortMappingDescription, err = soap.MarshalString(NewPortMappingDescription); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewLeaseDuration, err = soap.MarshalUi4(NewLeaseDuration); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Response structure.
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "AddPortMapping", request, response)
|
||||
}
|
||||
|
||||
// DeletePortMapping implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) DeletePortMapping(ctx context.Context, NewRemoteHost string, NewExternalPort uint16, NewProtocol string) (err error) {
|
||||
// Request structure.
|
||||
request := &struct {
|
||||
NewRemoteHost string
|
||||
NewExternalPort string
|
||||
NewProtocol string
|
||||
}{}
|
||||
if request.NewRemoteHost, err = soap.MarshalString(NewRemoteHost); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewExternalPort, err = soap.MarshalUi2(NewExternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewProtocol, err = soap.MarshalString(NewProtocol); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Response structure.
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "DeletePortMapping", request, response)
|
||||
}
|
||||
|
||||
// GetExternalIPAddress implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) GetExternalIPAddress(ctx context.Context) (NewExternalIPAddress string, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
// Response structure.
|
||||
response := &struct {
|
||||
NewExternalIPAddress string
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "GetExternalIPAddress", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if NewExternalIPAddress, err = soap.UnmarshalString(response.NewExternalIPAddress); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetStatusInfo implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) GetStatusInfo(ctx context.Context) (NewConnectionStatus string, NewLastConnectionError string, NewUptime uint32, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
// Response structure.
|
||||
response := &struct {
|
||||
NewConnectionStatus string
|
||||
NewLastConnectionError string
|
||||
NewUpTime string // NOTE: the "T" is capitalized here, per the spec, though it's lowercase in the newer UPnP spec
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "GetStatusInfo", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if NewConnectionStatus, err = soap.UnmarshalString(response.NewConnectionStatus); err != nil {
|
||||
return
|
||||
}
|
||||
if NewLastConnectionError, err = soap.UnmarshalString(response.NewLastConnectionError); err != nil {
|
||||
return
|
||||
}
|
||||
if NewUptime, err = soap.UnmarshalUi4(response.NewUpTime); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// legacyWANIPConnection1 is the same as internetgateway2.WANIPConnection1,
|
||||
// except using the old URN that starts with "urn:dslforum-org".
|
||||
//
|
||||
// See legacyWANPPPConnection1 for details on where this is defined.
|
||||
type legacyWANIPConnection1 struct {
|
||||
goupnp.ServiceClient
|
||||
}
|
||||
|
||||
// AddPortMapping implements upnpClient
|
||||
func (client *legacyWANIPConnection1) AddPortMapping(
|
||||
ctx context.Context,
|
||||
NewRemoteHost string,
|
||||
NewExternalPort uint16,
|
||||
NewProtocol string,
|
||||
NewInternalPort uint16,
|
||||
NewInternalClient string,
|
||||
NewEnabled bool,
|
||||
NewPortMappingDescription string,
|
||||
NewLeaseDuration uint32,
|
||||
) (err error) {
|
||||
// Request structure.
|
||||
request := &struct {
|
||||
NewRemoteHost string
|
||||
NewExternalPort string
|
||||
NewProtocol string
|
||||
NewInternalPort string
|
||||
NewInternalClient string
|
||||
NewEnabled string
|
||||
NewPortMappingDescription string
|
||||
NewLeaseDuration string
|
||||
}{}
|
||||
|
||||
if request.NewRemoteHost, err = soap.MarshalString(NewRemoteHost); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewExternalPort, err = soap.MarshalUi2(NewExternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewProtocol, err = soap.MarshalString(NewProtocol); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewInternalPort, err = soap.MarshalUi2(NewInternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewInternalClient, err = soap.MarshalString(NewInternalClient); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewEnabled, err = soap.MarshalBoolean(NewEnabled); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewPortMappingDescription, err = soap.MarshalString(NewPortMappingDescription); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewLeaseDuration, err = soap.MarshalUi4(NewLeaseDuration); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Response structure.
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "AddPortMapping", request, response)
|
||||
}
|
||||
|
||||
// DeletePortMapping implements upnpClient
|
||||
func (client *legacyWANIPConnection1) DeletePortMapping(ctx context.Context, NewRemoteHost string, NewExternalPort uint16, NewProtocol string) (err error) {
|
||||
// Request structure.
|
||||
request := &struct {
|
||||
NewRemoteHost string
|
||||
NewExternalPort string
|
||||
NewProtocol string
|
||||
}{}
|
||||
if request.NewRemoteHost, err = soap.MarshalString(NewRemoteHost); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewExternalPort, err = soap.MarshalUi2(NewExternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewProtocol, err = soap.MarshalString(NewProtocol); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Response structure.
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "DeletePortMapping", request, response)
|
||||
}
|
||||
|
||||
// GetExternalIPAddress implements upnpClient
|
||||
func (client *legacyWANIPConnection1) GetExternalIPAddress(ctx context.Context) (NewExternalIPAddress string, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
// Response structure.
|
||||
response := &struct {
|
||||
NewExternalIPAddress string
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "GetExternalIPAddress", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if NewExternalIPAddress, err = soap.UnmarshalString(response.NewExternalIPAddress); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetStatusInfo implements upnpClient
|
||||
func (client *legacyWANIPConnection1) GetStatusInfo(ctx context.Context) (NewConnectionStatus string, NewLastConnectionError string, NewUptime uint32, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
// Response structure.
|
||||
response := &struct {
|
||||
NewConnectionStatus string
|
||||
NewLastConnectionError string
|
||||
NewUpTime string // NOTE: the "T" is capitalized here, per the spec, though it's lowercase in the newer UPnP spec
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "GetStatusInfo", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if NewConnectionStatus, err = soap.UnmarshalString(response.NewConnectionStatus); err != nil {
|
||||
return
|
||||
}
|
||||
if NewLastConnectionError, err = soap.UnmarshalString(response.NewLastConnectionError); err != nil {
|
||||
return
|
||||
}
|
||||
if NewUptime, err = soap.UnmarshalUi4(response.NewUpTime); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1199,10 +1199,6 @@ var (
|
||||
// received a UPnP response from a port other than the UPnP port.
|
||||
metricUPnPResponseAlternatePort = clientmetric.NewCounter("portmap_upnp_response_alternate_port")
|
||||
|
||||
// metricUPnPSelectLegacy counts the number of times that a legacy
|
||||
// service was found in a UPnP response.
|
||||
metricUPnPSelectLegacy = clientmetric.NewCounter("portmap_upnp_select_legacy")
|
||||
|
||||
// metricUPnPSelectSingle counts the number of times that only a single
|
||||
// UPnP device was available in selectBestService.
|
||||
metricUPnPSelectSingle = clientmetric.NewCounter("portmap_upnp_select_single")
|
||||
|
||||
@@ -252,8 +252,7 @@ func getUPnPRootDevice(ctx context.Context, logf logger.Logf, debug DebugKnobs,
|
||||
}
|
||||
|
||||
// selectBestService picks the "best" service from the given UPnP root device
|
||||
// to use to create a port mapping. It may return (nil, nil) if no supported
|
||||
// service was found in the provided *goupnp.RootDevice.
|
||||
// to use to create a port mapping.
|
||||
//
|
||||
// loc is the parsed location that was used to fetch the given RootDevice.
|
||||
//
|
||||
@@ -289,19 +288,6 @@ func selectBestService(ctx context.Context, logf logger.Logf, root *goupnp.RootD
|
||||
clients = append(clients, v)
|
||||
}
|
||||
|
||||
// These are legacy services that were deprecated in 2015, but are
|
||||
// still in use by older devices; try them just in case.
|
||||
legacyClients, _ := goupnp.NewServiceClientsFromRootDevice(ctx, root, loc, urn_LegacyWANPPPConnection_1)
|
||||
metricUPnPSelectLegacy.Add(int64(len(legacyClients)))
|
||||
for _, client := range legacyClients {
|
||||
clients = append(clients, &legacyWANPPPConnection1{client})
|
||||
}
|
||||
legacyClients, _ = goupnp.NewServiceClientsFromRootDevice(ctx, root, loc, urn_LegacyWANIPConnection_1)
|
||||
metricUPnPSelectLegacy.Add(int64(len(legacyClients)))
|
||||
for _, client := range legacyClients {
|
||||
clients = append(clients, &legacyWANIPConnection1{client})
|
||||
}
|
||||
|
||||
// If we have no clients, then return right now; if we only have one,
|
||||
// just select and return it.
|
||||
if len(clients) == 0 {
|
||||
@@ -573,20 +559,6 @@ func (c *Client) tryUPnPPortmapWithDevice(
|
||||
return netip.AddrPort{}, nil, err
|
||||
}
|
||||
|
||||
// If we have no client, we cannot continue; this can happen if we get
|
||||
// a valid UPnP response that does not contain any of the service types
|
||||
// that we know how to use.
|
||||
if client == nil {
|
||||
// For debugging, print all available services that we aren't
|
||||
// using because they're not supported; use c.vlogf so we don't
|
||||
// spam the logs unless verbose debugging is turned on.
|
||||
rootDev.Device.VisitServices(func(s *goupnp.Service) {
|
||||
c.vlogf("unsupported UPnP service: Type=%q ID=%q ControlURL=%q", s.ServiceType, s.ServiceId, s.ControlURL.Str)
|
||||
})
|
||||
|
||||
return netip.AddrPort{}, nil, fmt.Errorf("no supported UPnP clients")
|
||||
}
|
||||
|
||||
// Start by trying to make a temporary lease with a duration.
|
||||
var newPort uint16
|
||||
newPort, err = addAnyPortMapping(
|
||||
|
||||
@@ -165,252 +165,6 @@ const (
|
||||
</device>
|
||||
<disabledForTestURLBase>http://10.0.0.1:2828</disabledForTestURLBase>
|
||||
</root>
|
||||
`
|
||||
|
||||
// Huawei, https://github.com/tailscale/tailscale/issues/10911
|
||||
huaweiRootDescXML = `<?xml version="1.0"?>
|
||||
<root xmlns="urn:schemas-upnp-org:device-1-0">
|
||||
<specVersion>
|
||||
<major>1</major>
|
||||
<minor>0</minor>
|
||||
</specVersion>
|
||||
<device>
|
||||
<deviceType>urn:dslforum-org:device:InternetGatewayDevice:1</deviceType>
|
||||
<friendlyName>HG531 V1</friendlyName>
|
||||
<manufacturer>Huawei Technologies Co., Ltd.</manufacturer>
|
||||
<manufacturerURL>http://www.huawei.com</manufacturerURL>
|
||||
<modelDescription>Huawei Home Gateway</modelDescription>
|
||||
<modelName>HG531 V1</modelName>
|
||||
<modelNumber>Huawei Model</modelNumber>
|
||||
<modelURL>http://www.huawei.com</modelURL>
|
||||
<serialNumber>G6J8W15326003974</serialNumber>
|
||||
<UDN>uuid:00e0fc37-2626-2828-2600-587f668bdd9a</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:www-huawei-com:service:DeviceConfig:1</serviceType>
|
||||
<serviceId>urn:www-huawei-com:serviceId:DeviceConfig1</serviceId>
|
||||
<SCPDURL>/desc/DevCfg.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/DeviceConfig_1</controlURL>
|
||||
<eventSubURL>/evt/DeviceConfig_1</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:LANConfigSecurity:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:LANConfigSecurity1</serviceId>
|
||||
<SCPDURL>/desc/LANSec.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/LANConfigSecurity_1</controlURL>
|
||||
<eventSubURL>/evt/LANConfigSecurity_1</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:Layer3Forwarding:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:Layer3Forwarding1</serviceId>
|
||||
<SCPDURL>/desc/L3Fwd.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/Layer3Forwarding_1</controlURL>
|
||||
<eventSubURL>/evt/Layer3Forwarding_1</eventSubURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
<deviceList>
|
||||
<device>
|
||||
<deviceType>urn:dslforum-org:device:WANDevice:1</deviceType>
|
||||
<friendlyName>WANDevice</friendlyName>
|
||||
<manufacturer>Huawei Technologies Co., Ltd.</manufacturer>
|
||||
<manufacturerURL>http://www.huawei.com</manufacturerURL>
|
||||
<modelDescription>Huawei Home Gateway</modelDescription>
|
||||
<modelName>HG531 V1</modelName>
|
||||
<modelNumber>Huawei Model</modelNumber>
|
||||
<modelURL>http://www.huawei.com</modelURL>
|
||||
<serialNumber>G6J8W15326003974</serialNumber>
|
||||
<UDN>uuid:00e0fc37-2626-2828-2601-587f668bdd9a</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WANDSLInterfaceConfig:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WANDSLInterfaceConfig1</serviceId>
|
||||
<SCPDURL>/desc/WanDslIfCfg.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WANDSLInterfaceConfig_1</controlURL>
|
||||
<eventSubURL>/evt/WANDSLInterfaceConfig_1</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WANCommonInterfaceConfig:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WANCommonInterfaceConfig1</serviceId>
|
||||
<SCPDURL>/desc/WanCommonIfc1.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WANCommonInterfaceConfig_1</controlURL>
|
||||
<eventSubURL>/evt/WANCommonInterfaceConfig_1</eventSubURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
<deviceList>
|
||||
<device>
|
||||
<deviceType>urn:dslforum-org:device:WANConnectionDevice:1</deviceType>
|
||||
<friendlyName>WANConnectionDevice</friendlyName>
|
||||
<manufacturer>Huawei Technologies Co., Ltd.</manufacturer>
|
||||
<manufacturerURL>http://www.huawei.com</manufacturerURL>
|
||||
<modelDescription>Huawei Home Gateway</modelDescription>
|
||||
<modelName>HG531 V1</modelName>
|
||||
<modelNumber>Huawei Model</modelNumber>
|
||||
<modelURL>http://www.huawei.com</modelURL>
|
||||
<serialNumber>G6J8W15326003974</serialNumber>
|
||||
<UDN>uuid:00e0fc37-2626-2828-2603-587f668bdd9a</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WANPPPConnection:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WANPPPConnection1</serviceId>
|
||||
<SCPDURL>/desc/WanPppConn.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WANPPPConnection_1</controlURL>
|
||||
<eventSubURL>/evt/WANPPPConnection_1</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WANEthernetConnectionManagement:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WANEthernetConnectionManagement1</serviceId>
|
||||
<SCPDURL>/desc/WanEthConnMgt.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WANEthernetConnectionManagement_1</controlURL>
|
||||
<eventSubURL>/evt/WANEthernetConnectionManagement_1</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WANDSLLinkConfig:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WANDSLLinkConfig1</serviceId>
|
||||
<SCPDURL>/desc/WanDslLink.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WANDSLLinkConfig_1</controlURL>
|
||||
<eventSubURL>/evt/WANDSLLinkConfig_1</eventSubURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
</device>
|
||||
</deviceList>
|
||||
</device>
|
||||
<device>
|
||||
<deviceType>urn:dslforum-org:device:LANDevice:1</deviceType>
|
||||
<friendlyName>LANDevice</friendlyName>
|
||||
<manufacturer>Huawei Technologies Co., Ltd.</manufacturer>
|
||||
<manufacturerURL>http://www.huawei.com</manufacturerURL>
|
||||
<modelDescription>Huawei Home Gateway</modelDescription>
|
||||
<modelName>HG531 V1</modelName>
|
||||
<modelNumber>Huawei Model</modelNumber>
|
||||
<modelURL>http://www.huawei.com</modelURL>
|
||||
<serialNumber>G6J8W15326003974</serialNumber>
|
||||
<UDN>uuid:00e0fc37-2626-2828-2602-587f668bdd9a</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WLANConfiguration:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WLANConfiguration4</serviceId>
|
||||
<SCPDURL>/desc/WLANCfg.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WLANConfiguration_4</controlURL>
|
||||
<eventSubURL>/evt/WLANConfiguration_4</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WLANConfiguration:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WLANConfiguration3</serviceId>
|
||||
<SCPDURL>/desc/WLANCfg.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WLANConfiguration_3</controlURL>
|
||||
<eventSubURL>/evt/WLANConfiguration_3</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WLANConfiguration:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WLANConfiguration2</serviceId>
|
||||
<SCPDURL>/desc/WLANCfg.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WLANConfiguration_2</controlURL>
|
||||
<eventSubURL>/evt/WLANConfiguration_2</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WLANConfiguration:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WLANConfiguration1</serviceId>
|
||||
<SCPDURL>/desc/WLANCfg.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WLANConfiguration_1</controlURL>
|
||||
<eventSubURL>/evt/WLANConfiguration_1</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:LANHostConfigManagement:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:LANHostConfigManagement1</serviceId>
|
||||
<SCPDURL>/desc/LanHostCfgMgmt.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/LANHostConfigManagement_1</controlURL>
|
||||
<eventSubURL>/evt/LANHostConfigManagement_1</eventSubURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
</device>
|
||||
</deviceList>
|
||||
<presentationURL>http://127.0.0.1</presentationURL>
|
||||
</device>
|
||||
</root>
|
||||
`
|
||||
|
||||
noSupportedServicesRootDesc = `<?xml version="1.0"?>
|
||||
<root xmlns="urn:schemas-upnp-org:device-1-0">
|
||||
<specVersion>
|
||||
<major>1</major>
|
||||
<minor>0</minor>
|
||||
</specVersion>
|
||||
<device>
|
||||
<deviceType>urn:dslforum-org:device:InternetGatewayDevice:1</deviceType>
|
||||
<friendlyName>Fake Router</friendlyName>
|
||||
<manufacturer>Tailscale, Inc</manufacturer>
|
||||
<manufacturerURL>http://www.tailscale.com</manufacturerURL>
|
||||
<modelDescription>Fake Router</modelDescription>
|
||||
<modelName>Test Model</modelName>
|
||||
<modelNumber>v1</modelNumber>
|
||||
<modelURL>http://www.tailscale.com</modelURL>
|
||||
<serialNumber>123456789</serialNumber>
|
||||
<UDN>uuid:11111111-2222-3333-4444-555555555555</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:schemas-microsoft-com:service:OSInfo:1</serviceType>
|
||||
<serviceId>urn:microsoft-com:serviceId:OSInfo1</serviceId>
|
||||
<SCPDURL>/osinfo.xml</SCPDURL>
|
||||
<controlURL>/upnp/control/aaaaaaaaaa/osinfo</controlURL>
|
||||
<eventSubURL>/upnp/event/aaaaaaaaaa/osinfo</eventSubURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
<deviceList>
|
||||
<device>
|
||||
<deviceType>urn:schemas-upnp-org:device:WANDevice:1</deviceType>
|
||||
<friendlyName>WANDevice</friendlyName>
|
||||
<manufacturer>Tailscale, Inc</manufacturer>
|
||||
<manufacturerURL>http://www.tailscale.com</manufacturerURL>
|
||||
<modelDescription>Tailscale Test Router</modelDescription>
|
||||
<modelName>Test Model</modelName>
|
||||
<modelNumber>v1</modelNumber>
|
||||
<modelURL>http://www.tailscale.com</modelURL>
|
||||
<serialNumber>123456789</serialNumber>
|
||||
<UDN>uuid:11111111-2222-3333-4444-555555555555</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1</serviceType>
|
||||
<serviceId>urn:upnp-org:serviceId:WANCommonIFC1</serviceId>
|
||||
<controlURL>/ctl/bbbbbbbb</controlURL>
|
||||
<eventSubURL>/evt/bbbbbbbb</eventSubURL>
|
||||
<SCPDURL>/WANCfg.xml</SCPDURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
<deviceList>
|
||||
<device>
|
||||
<deviceType>urn:schemas-upnp-org:device:WANConnectionDevice:1</deviceType>
|
||||
<friendlyName>WANConnectionDevice</friendlyName>
|
||||
<manufacturer>Tailscale, Inc</manufacturer>
|
||||
<manufacturerURL>http://www.tailscale.com</manufacturerURL>
|
||||
<modelDescription>Tailscale Test Router</modelDescription>
|
||||
<modelName>Test Model</modelName>
|
||||
<modelNumber>v1</modelNumber>
|
||||
<modelURL>http://www.tailscale.com</modelURL>
|
||||
<serialNumber>123456789</serialNumber>
|
||||
<UDN>uuid:11111111-2222-3333-4444-555555555555</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:tailscale:service:SomethingElse:1</serviceType>
|
||||
<serviceId>urn:upnp-org:serviceId:TailscaleSomethingElse</serviceId>
|
||||
<SCPDURL>/desc/SomethingElse.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/SomethingElse_1</controlURL>
|
||||
<eventSubURL>/evt/SomethingElse_1</eventSubURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
</device>
|
||||
</deviceList>
|
||||
</device>
|
||||
</deviceList>
|
||||
<presentationURL>http://127.0.0.1</presentationURL>
|
||||
</device>
|
||||
</root>
|
||||
`
|
||||
)
|
||||
|
||||
@@ -479,19 +233,6 @@ func TestGetUPnPClient(t *testing.T) {
|
||||
"*internetgateway2.WANIPConnection1",
|
||||
"saw UPnP type WANIPConnection1 at http://127.0.0.1:NNN/rootDesc.xml; MikroTik Router (MikroTik), method=none\n",
|
||||
},
|
||||
{
|
||||
"huawei",
|
||||
huaweiRootDescXML,
|
||||
"*portmapper.legacyWANPPPConnection1",
|
||||
"saw UPnP type *portmapper.legacyWANPPPConnection1 at http://127.0.0.1:NNN/rootDesc.xml; HG531 V1 (Huawei Technologies Co., Ltd.), method=single\n",
|
||||
},
|
||||
{
|
||||
"not_supported",
|
||||
noSupportedServicesRootDesc,
|
||||
"<nil>",
|
||||
"",
|
||||
},
|
||||
|
||||
// TODO(bradfitz): find a PPP one in the wild
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -634,99 +375,6 @@ func TestGetUPnPPortMapping(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetUPnPPortMapping_NoValidServices tests that getUPnPPortMapping doesn't
|
||||
// crash when a valid UPnP response with no supported services is discovered
|
||||
// and parsed.
|
||||
//
|
||||
// See https://github.com/tailscale/tailscale/issues/10911
|
||||
func TestGetUPnPPortMapping_NoValidServices(t *testing.T) {
|
||||
igd, err := NewTestIGD(t.Logf, TestIGDOptions{UPnP: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer igd.Close()
|
||||
|
||||
igd.SetUPnPHandler(&upnpServer{
|
||||
t: t,
|
||||
Desc: noSupportedServicesRootDesc,
|
||||
})
|
||||
|
||||
c := newTestClient(t, igd)
|
||||
defer c.Close()
|
||||
c.debug.VerboseLogs = true
|
||||
|
||||
ctx := context.Background()
|
||||
res, err := c.Probe(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Probe: %v", err)
|
||||
}
|
||||
if !res.UPnP {
|
||||
t.Errorf("didn't detect UPnP")
|
||||
}
|
||||
|
||||
gw, myIP, ok := c.gatewayAndSelfIP()
|
||||
if !ok {
|
||||
t.Fatalf("could not get gateway and self IP")
|
||||
}
|
||||
|
||||
// This shouldn't panic
|
||||
_, ok = c.getUPnPPortMapping(ctx, gw, netip.AddrPortFrom(myIP, 12345), 0)
|
||||
if ok {
|
||||
t.Fatal("did not expect to get UPnP port mapping")
|
||||
}
|
||||
}
|
||||
|
||||
// Tests the legacy behaviour with the pre-UPnP standard portmapping service.
|
||||
func TestGetUPnPPortMapping_Legacy(t *testing.T) {
|
||||
igd, err := NewTestIGD(t.Logf, TestIGDOptions{UPnP: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer igd.Close()
|
||||
|
||||
// This is a very basic fake UPnP server handler.
|
||||
handlers := map[string]any{
|
||||
"AddPortMapping": testLegacyAddPortMappingResponse,
|
||||
"GetExternalIPAddress": testLegacyGetExternalIPAddressResponse,
|
||||
"GetStatusInfo": testLegacyGetStatusInfoResponse,
|
||||
"DeletePortMapping": "", // Do nothing for test
|
||||
}
|
||||
|
||||
igd.SetUPnPHandler(&upnpServer{
|
||||
t: t,
|
||||
Desc: huaweiRootDescXML,
|
||||
Control: map[string]map[string]any{
|
||||
"/ctrlt/WANPPPConnection_1": handlers,
|
||||
},
|
||||
})
|
||||
|
||||
c := newTestClient(t, igd)
|
||||
defer c.Close()
|
||||
c.debug.VerboseLogs = true
|
||||
|
||||
ctx := context.Background()
|
||||
res, err := c.Probe(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Probe: %v", err)
|
||||
}
|
||||
if !res.UPnP {
|
||||
t.Errorf("didn't detect UPnP")
|
||||
}
|
||||
|
||||
gw, myIP, ok := c.gatewayAndSelfIP()
|
||||
if !ok {
|
||||
t.Fatalf("could not get gateway and self IP")
|
||||
}
|
||||
|
||||
ext, ok := c.getUPnPPortMapping(ctx, gw, netip.AddrPortFrom(myIP, 12345), 0)
|
||||
if !ok {
|
||||
t.Fatal("could not get UPnP port mapping")
|
||||
}
|
||||
if got, want := ext.Addr(), netip.MustParseAddr("123.123.123.123"); got != want {
|
||||
t.Errorf("bad external address; got %v want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUPnPPortMappingNoResponses(t *testing.T) {
|
||||
igd, err := NewTestIGD(t.Logf, TestIGDOptions{UPnP: true})
|
||||
if err != nil {
|
||||
@@ -1028,33 +676,3 @@ const testGetStatusInfoResponse = `<?xml version="1.0"?>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
`
|
||||
|
||||
const testLegacyAddPortMappingResponse = `<?xml version="1.0"?>
|
||||
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
<s:Body>
|
||||
<u:AddPortMappingResponse xmlns:u="urn:dslforum-org:service:WANPPPConnection:1"/>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
`
|
||||
|
||||
const testLegacyGetExternalIPAddressResponse = `<?xml version="1.0"?>
|
||||
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
<s:Body>
|
||||
<u:GetExternalIPAddressResponse xmlns:u="urn:dslforum-org:service:WANPPPConnection:1">
|
||||
<NewExternalIPAddress>123.123.123.123</NewExternalIPAddress>
|
||||
</u:GetExternalIPAddressResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
`
|
||||
|
||||
const testLegacyGetStatusInfoResponse = `<?xml version="1.0"?>
|
||||
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
<s:Body>
|
||||
<u:GetStatusInfoResponse xmlns:u="urn:dslforum-org:service:WANPPPConnection:1">
|
||||
<NewConnectionStatus>Connected</NewConnectionStatus>
|
||||
<NewLastConnectionError>ERROR_NONE</NewLastConnectionError>
|
||||
<NewUpTime>9999</NewUpTime>
|
||||
</u:GetStatusInfoResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
`
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package stunserver implements a STUN server. The package publishes a number of stats
|
||||
// to expvar under the top level label "stun". Logs are sent to the standard log package.
|
||||
package stunserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"expvar"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"tailscale.com/metrics"
|
||||
"tailscale.com/net/stun"
|
||||
)
|
||||
|
||||
var (
|
||||
stats = new(metrics.Set)
|
||||
stunDisposition = &metrics.LabelMap{Label: "disposition"}
|
||||
stunAddrFamily = &metrics.LabelMap{Label: "family"}
|
||||
stunReadError = stunDisposition.Get("read_error")
|
||||
stunNotSTUN = stunDisposition.Get("not_stun")
|
||||
stunWriteError = stunDisposition.Get("write_error")
|
||||
stunSuccess = stunDisposition.Get("success")
|
||||
|
||||
stunIPv4 = stunAddrFamily.Get("ipv4")
|
||||
stunIPv6 = stunAddrFamily.Get("ipv6")
|
||||
)
|
||||
|
||||
func init() {
|
||||
stats.Set("counter_requests", stunDisposition)
|
||||
stats.Set("counter_addrfamily", stunAddrFamily)
|
||||
expvar.Publish("stun", stats)
|
||||
}
|
||||
|
||||
type STUNServer struct {
|
||||
ctx context.Context // ctx signals service shutdown
|
||||
pc *net.UDPConn // pc is the UDP listener
|
||||
}
|
||||
|
||||
// New creates a new STUN server. The server is shutdown when ctx is done.
|
||||
func New(ctx context.Context) *STUNServer {
|
||||
return &STUNServer{ctx: ctx}
|
||||
}
|
||||
|
||||
// Listen binds the listen socket for the server at listenAddr.
|
||||
func (s *STUNServer) Listen(listenAddr string) error {
|
||||
uaddr, err := net.ResolveUDPAddr("udp", listenAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.pc, err = net.ListenUDP("udp", uaddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("STUN server listening on %v", s.LocalAddr())
|
||||
// close the listener on shutdown in order to break out of the read loop
|
||||
go func() {
|
||||
<-s.ctx.Done()
|
||||
s.pc.Close()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serve starts serving responses to STUN requests. Listen must be called before Serve.
|
||||
func (s *STUNServer) Serve() error {
|
||||
var buf [64 << 10]byte
|
||||
var (
|
||||
n int
|
||||
ua *net.UDPAddr
|
||||
err error
|
||||
)
|
||||
for {
|
||||
n, ua, err = s.pc.ReadFromUDP(buf[:])
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {
|
||||
return nil
|
||||
}
|
||||
log.Printf("STUN ReadFrom: %v", err)
|
||||
time.Sleep(time.Second)
|
||||
stunReadError.Add(1)
|
||||
continue
|
||||
}
|
||||
pkt := buf[:n]
|
||||
if !stun.Is(pkt) {
|
||||
stunNotSTUN.Add(1)
|
||||
continue
|
||||
}
|
||||
txid, err := stun.ParseBindingRequest(pkt)
|
||||
if err != nil {
|
||||
stunNotSTUN.Add(1)
|
||||
continue
|
||||
}
|
||||
if ua.IP.To4() != nil {
|
||||
stunIPv4.Add(1)
|
||||
} else {
|
||||
stunIPv6.Add(1)
|
||||
}
|
||||
addr, _ := netip.AddrFromSlice(ua.IP)
|
||||
res := stun.Response(txid, netip.AddrPortFrom(addr, uint16(ua.Port)))
|
||||
_, err = s.pc.WriteTo(res, ua)
|
||||
if err != nil {
|
||||
stunWriteError.Add(1)
|
||||
} else {
|
||||
stunSuccess.Add(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ListenAndServe starts the STUN server on listenAddr.
|
||||
func (s *STUNServer) ListenAndServe(listenAddr string) error {
|
||||
if err := s.Listen(listenAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Serve()
|
||||
}
|
||||
|
||||
// LocalAddr returns the local address of the STUN server. It must not be called before ListenAndServe.
|
||||
func (s *STUNServer) LocalAddr() net.Addr {
|
||||
return s.pc.LocalAddr()
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package stunserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"tailscale.com/net/stun"
|
||||
"tailscale.com/util/must"
|
||||
)
|
||||
|
||||
func TestSTUNServer(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
s := New(ctx)
|
||||
must.Do(s.Listen("localhost:0"))
|
||||
var w sync.WaitGroup
|
||||
w.Add(1)
|
||||
var serveErr error
|
||||
go func() {
|
||||
defer w.Done()
|
||||
serveErr = s.Serve()
|
||||
}()
|
||||
|
||||
c := must.Get(net.DialUDP("udp", nil, s.LocalAddr().(*net.UDPAddr)))
|
||||
defer c.Close()
|
||||
c.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
txid := stun.NewTxID()
|
||||
_, err := c.Write(stun.Request(txid))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write STUN request: %v", err)
|
||||
}
|
||||
var buf [64 << 10]byte
|
||||
n, err := c.Read(buf[:])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read STUN response: %v", err)
|
||||
}
|
||||
if !stun.Is(buf[:n]) {
|
||||
t.Fatalf("response is not STUN")
|
||||
}
|
||||
tid, _, err := stun.ParseResponse(buf[:n])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse STUN response: %v", err)
|
||||
}
|
||||
if tid != txid {
|
||||
t.Fatalf("STUN response has wrong transaction ID; got %d, want %d", tid, txid)
|
||||
}
|
||||
|
||||
cancel()
|
||||
w.Wait()
|
||||
if serveErr != nil {
|
||||
t.Fatalf("failed to listen and serve: %v", serveErr)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkServerSTUN(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
s := New(ctx)
|
||||
s.Listen("localhost:0")
|
||||
go s.Serve()
|
||||
addr := s.LocalAddr().(*net.UDPAddr)
|
||||
|
||||
var resBuf [1500]byte
|
||||
cc, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
tx := stun.NewTxID()
|
||||
req := stun.Request(tx)
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := cc.WriteToUDP(req, addr); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_, _, err := cc.ReadFromUDP(resBuf[:])
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/version"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -74,7 +72,7 @@ func localTCPPortAndTokenDarwin() (port int, token string, err error) {
|
||||
|
||||
if dir := os.Getenv("TS_MACOS_CLI_SHARED_DIR"); dir != "" {
|
||||
// First see if we're running as the non-AppStore "macsys" variant.
|
||||
if version.IsMacSysExt() {
|
||||
if strings.Contains(os.Getenv("HOME"), "/Containers/io.tailscale.ipn.macsys/") {
|
||||
if port, token, err := localTCPPortAndTokenMacsys(); err == nil {
|
||||
return port, token, nil
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user