├── .gitignore ├── example ├── go.mod ├── Dockerfile ├── traefik.yaml ├── main.go └── docker-compose.yaml ├── Dockerfile ├── .github ├── dependabot.yml └── workflows │ └── build.yaml ├── LICENSE ├── go.mod ├── README.md ├── main.go └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | .tags 2 | tailscale-forward-auth 3 | echo-server 4 | -------------------------------------------------------------------------------- /example/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kevin-hanselman/echo-server 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /example/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.18-alpine AS build-env 2 | 3 | WORKDIR /go/src/echo-server 4 | 5 | COPY main.go go.mod ./ 6 | RUN go install . 7 | 8 | FROM alpine 9 | COPY --from=build-env /go/bin/* /usr/local/bin/ 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.20-alpine AS build-env 2 | 3 | WORKDIR /go/src/tailscale-forward-auth 4 | 5 | COPY go.mod go.sum ./ 6 | RUN go mod download 7 | 8 | COPY main.go ./ 9 | RUN CGO_ENABLED=0 go install . 10 | 11 | FROM scratch 12 | COPY --from=build-env /go/bin/tailscale-forward-auth /tailscale-forward-auth 13 | ENTRYPOINT ["/tailscale-forward-auth"] 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - 4 | package-ecosystem: "gomod" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | - 9 | package-ecosystem: "docker" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | - 14 | package-ecosystem: "github-actions" 15 | directory: "/" 16 | schedule: 17 | interval: "weekly" 18 | -------------------------------------------------------------------------------- /example/traefik.yaml: -------------------------------------------------------------------------------- 1 | api: 2 | insecure: true 3 | 4 | log: 5 | level: INFO 6 | 7 | providers: 8 | docker: 9 | exposedByDefault: false 10 | # This is needed to reference the middleware(s) defined below with '@file' 11 | file: 12 | filename: /etc/traefik/traefik.yaml 13 | 14 | http: 15 | middlewares: 16 | tailscale-auth: 17 | forwardAuth: 18 | address: "http://tailscale_forward_auth" 19 | authResponseHeadersRegex: '^Tailscale-' 20 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net" 7 | "net/http" 8 | "encoding/json" 9 | ) 10 | 11 | var ( 12 | listenAddr = flag.String("addr", "127.0.0.1:", "the TCP address to listen on") 13 | ) 14 | 15 | func main() { 16 | flag.Parse() 17 | if *listenAddr == "" { 18 | log.Fatal("listen address not set") 19 | } 20 | 21 | mux := http.NewServeMux() 22 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 23 | e := json.NewEncoder(w) 24 | e.SetIndent("", " ") 25 | e.Encode(r.Header) 26 | }) 27 | 28 | ln, err := net.Listen("tcp", *listenAddr) 29 | if err != nil { 30 | log.Fatalf("can't listen on %s: %v", *listenAddr, err) 31 | } 32 | defer ln.Close() 33 | 34 | log.Printf("listening on %s", ln.Addr()) 35 | log.Fatal(http.Serve(ln, mux)) 36 | } 37 | -------------------------------------------------------------------------------- /example/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | traefik: 5 | image: traefik:latest 6 | ports: 7 | - 80:80 8 | - 8080:8080 9 | networks: 10 | - frontend 11 | - tailscale_forward_auth 12 | volumes: 13 | - /var/run/docker.sock:/var/run/docker.sock:ro 14 | - ./traefik.yaml:/etc/traefik/traefik.yaml 15 | 16 | tailscale_forward_auth: 17 | build: .. 18 | command: ["-addr=0.0.0.0:80", "-debug"] 19 | networks: 20 | - tailscale_forward_auth 21 | volumes: 22 | - /var/run/tailscale/:/var/run/tailscale 23 | 24 | echo: 25 | build: . 26 | command: ['echo-server', '-addr=0.0.0.0:9001'] 27 | networks: 28 | - frontend 29 | labels: 30 | - traefik.enable=true 31 | - "traefik.http.routers.echo.rule=Path(`/echo`)" 32 | - traefik.http.services.echo.loadbalancer.server.port=9001 33 | - traefik.http.routers.echo.middlewares=tailscale-auth@file 34 | 35 | networks: 36 | frontend: 37 | tailscale_forward_auth: 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Kevin Hanselman 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kevin-hanselman/tailscale-forward-auth 2 | 3 | go 1.20 4 | 5 | require tailscale.com v1.40.1 6 | 7 | require ( 8 | filippo.io/edwards25519 v1.0.0 // indirect 9 | github.com/Microsoft/go-winio v0.6.1 // indirect 10 | github.com/akutz/memconn v0.1.0 // indirect 11 | github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect 12 | github.com/fxamacker/cbor/v2 v2.4.0 // indirect 13 | github.com/google/go-cmp v0.5.9 // indirect 14 | github.com/hdevalence/ed25519consensus v0.1.0 // indirect 15 | github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect 16 | github.com/jsimonetti/rtnetlink v1.3.3 // indirect 17 | github.com/mdlayher/netlink v1.7.2 // indirect 18 | github.com/mdlayher/socket v0.4.1 // indirect 19 | github.com/mitchellh/go-ps v1.0.0 // indirect 20 | github.com/x448/float16 v0.8.4 // indirect 21 | go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect 22 | go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect 23 | go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 // indirect 24 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect 25 | golang.org/x/crypto v0.9.0 // indirect 26 | golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect 27 | golang.org/x/mod v0.10.0 // indirect 28 | golang.org/x/net v0.10.0 // indirect 29 | golang.org/x/sync v0.2.0 // indirect 30 | golang.org/x/sys v0.8.0 // indirect 31 | golang.org/x/text v0.9.0 // indirect 32 | golang.org/x/tools v0.9.1 // indirect 33 | golang.zx2c4.com/wireguard/windows v0.5.3 // indirect 34 | inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: ['main'] 6 | tags: ['v*.*.*'] 7 | pull_request: 8 | branches: ['main'] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | golangci-lint: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version: '1.20.4' 21 | - uses: actions/checkout@v4 22 | - 23 | name: golangci-lint 24 | uses: golangci/golangci-lint-action@v6 25 | with: 26 | version: latest 27 | 28 | build: 29 | runs-on: ubuntu-latest 30 | permissions: 31 | packages: write 32 | steps: 33 | - 34 | uses: actions/checkout@v4 35 | - 36 | name: Generate image metadata 37 | id: meta 38 | uses: docker/metadata-action@v5 39 | with: 40 | images: ghcr.io/${{ github.repository }} 41 | # Generate Docker tags based on the following events/attributes 42 | # See: https://github.com/docker/metadata-action#tags-input 43 | tags: | 44 | type=semver,pattern=v{{version}} 45 | type=semver,pattern=v{{major}}.{{minor}} 46 | type=semver,pattern=v{{major}} 47 | type=edge 48 | type=ref,event=pr 49 | - 50 | name: Set up QEMU 51 | uses: docker/setup-qemu-action@v3 52 | - 53 | name: Set up Docker Buildx 54 | uses: docker/setup-buildx-action@v3 55 | - 56 | name: Login to GHCR 57 | if: github.event_name != 'pull_request' 58 | uses: docker/login-action@v3 59 | with: 60 | registry: ghcr.io 61 | username: ${{ github.repository_owner }} 62 | password: ${{ secrets.GITHUB_TOKEN }} 63 | - 64 | name: Build and push 65 | uses: docker/build-push-action@v5 66 | with: 67 | context: . 68 | push: ${{ github.event_name != 'pull_request' }} 69 | platforms: linux/amd64,linux/arm64 70 | tags: ${{ steps.meta.outputs.tags }} 71 | labels: ${{ steps.meta.outputs.labels }} 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tailscale-forward-auth 2 | 3 | This is a basic example of how to implement a Tailscale authentication server 4 | for general use with proxies. It is derived from the [Tailscale nginx-auth 5 | command](https://github.com/tailscale/tailscale/blob/741ae9956e674177687062b5499a80db83505076/cmd/nginx-auth/README.md), 6 | but it is decoupled from NGINX and packaged in a Docker image. 7 | 8 | ## Traefik example 9 | 10 | **Don't run this example in production; it's not secure.** 11 | 12 | The `example` directory contains an example of running the server as a 13 | [ForwardAuth middleware in 14 | Traefik](https://doc.traefik.io/traefik/middlewares/http/forwardauth/). It 15 | assumes that you have Docker and Docker Compose available on your machine, and 16 | that tailscaled is running and authenticated on the same machine (using the 17 | tailscaled UNIX socket at the default location). 18 | 19 | To run the example: 20 | 21 | cd example 22 | docker-compose up -d 23 | docker-compose logs -f 24 | 25 | From the same machine, send an HTTP request to the proxied application using 26 | its local IP address. You should receive a 401 error: 27 | 28 | ``` 29 | $ curl -v localhost/echo 30 | * Trying 127.0.0.1:80... 31 | * Connected to localhost (127.0.0.1) port 80 (#0) 32 | > GET /echo HTTP/1.1 33 | > Host: localhost 34 | > User-Agent: curl/7.83.0 35 | > Accept: */* 36 | > 37 | * Mark bundle as not supporting multiuse 38 | < HTTP/1.1 401 Unauthorized 39 | < Content-Length: 0 40 | < Date: Sat, 07 May 2022 18:44:39 GMT 41 | < 42 | * Connection #0 to host localhost left intact 43 | ``` 44 | 45 | Now send an HTTP request using the Tailscale IP. You should now receive 46 | a response from your echo server. Note the added `Tailscale-` fields: 47 | 48 | ``` 49 | $ curl $(tailscale ip -4)/echo 50 | { 51 | "Accept": [ 52 | "*/*" 53 | ], 54 | "Accept-Encoding": [ 55 | "gzip" 56 | ], 57 | "Tailscale-Login": [ 58 | "jane-doe" 59 | ], 60 | "Tailscale-Name": [ 61 | "Jane Doe" 62 | ], 63 | "Tailscale-Profile-Picture": [], 64 | "Tailscale-Tailnet": [ 65 | "jane-doe.example" 66 | ], 67 | "Tailscale-User": [ 68 | "jane-doe@example" 69 | ], 70 | "User-Agent": [ 71 | "curl" 72 | ], 73 | "X-Forwarded-For": [ 74 | "100.x.x.x" 75 | ], 76 | "X-Forwarded-Host": [ 77 | "100.x.x.x" 78 | ], 79 | "X-Forwarded-Port": [ 80 | "80" 81 | ], 82 | "X-Forwarded-Proto": [ 83 | "http" 84 | ], 85 | "X-Forwarded-Server": [ 86 | "xxxxxxxxxx" 87 | ], 88 | "X-Real-Ip": [ 89 | "100.x.x.x" 90 | ] 91 | } 92 | ``` 93 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // This code is forked from Tailscale codebase which is governed by 2 | // a BSD-style licence. See https://github.com/tailscale/tailscale 3 | // 4 | // The link below is the code from which this code originates: 5 | // https://github.com/tailscale/tailscale/blob/741ae9956e674177687062b5499a80db83505076/cmd/nginx-auth/nginx-auth.go 6 | 7 | package main 8 | 9 | import ( 10 | "flag" 11 | "log" 12 | "net" 13 | "net/http" 14 | "net/netip" 15 | "net/url" 16 | "strings" 17 | 18 | "tailscale.com/client/tailscale" 19 | ) 20 | 21 | var ( 22 | listenProto = flag.String("network", "tcp", "type of network to listen on, defaults to tcp") 23 | listenAddr = flag.String("addr", "127.0.0.1:", "address to listen on, defaults to 127.0.0.1:") 24 | headerRemoteIP = flag.String("remote-ip-header", "X-Forwarded-For", "HTTP header field containing the remote IP") 25 | headerRemotePort = flag.String("remote-port-header", "X-Forwarded-Port", "HTTP header field containing the remote port") 26 | debug = flag.Bool("debug", false, "enable debug logging") 27 | ) 28 | 29 | func main() { 30 | flag.Parse() 31 | if *listenAddr == "" { 32 | log.Fatal("listen address not set") 33 | } 34 | 35 | mux := http.NewServeMux() 36 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 37 | if *debug { 38 | log.Printf("received request with header %+v", r.Header) 39 | } 40 | 41 | remoteHost := r.Header.Get(*headerRemoteIP) 42 | if remoteHost == "" { 43 | w.WriteHeader(http.StatusBadRequest) 44 | log.Printf("missing header %s", *headerRemoteIP) 45 | return 46 | } 47 | 48 | remotePort := r.Header.Get(*headerRemotePort) 49 | if remotePort == "" { 50 | w.WriteHeader(http.StatusBadRequest) 51 | log.Printf("missing header %s", *headerRemotePort) 52 | return 53 | } 54 | 55 | remoteAddr, err := netip.ParseAddrPort(net.JoinHostPort(remoteHost, remotePort)) 56 | if err != nil { 57 | w.WriteHeader(http.StatusUnauthorized) 58 | log.Printf("remote address and port are not valid: %v", err) 59 | return 60 | } 61 | 62 | client := &tailscale.LocalClient{} 63 | info, err := client.WhoIs(r.Context(), remoteAddr.String()) 64 | if err != nil { 65 | w.WriteHeader(http.StatusUnauthorized) 66 | log.Printf("can't look up %s: %v", remoteAddr, err) 67 | return 68 | } 69 | 70 | if len(info.Node.Tags) != 0 { 71 | w.WriteHeader(http.StatusForbidden) 72 | log.Printf("node %s is tagged", info.Node.Hostinfo.Hostname()) 73 | return 74 | } 75 | 76 | // tailnet of connected node. When accessing shared nodes, this 77 | // will be empty because the tailnet of the sharee is not exposed. 78 | var tailnet string 79 | 80 | if !info.Node.Hostinfo.ShareeNode() { 81 | var ok bool 82 | _, tailnet, ok = strings.Cut(info.Node.Name, info.Node.ComputedName+".") 83 | if !ok { 84 | w.WriteHeader(http.StatusUnauthorized) 85 | log.Printf("can't extract tailnet name from hostname %q", info.Node.Name) 86 | return 87 | } 88 | tailnet = strings.TrimSuffix(tailnet, ".beta.tailscale.net") 89 | } 90 | 91 | if expectedTailnet := r.Header.Get("Expected-Tailnet"); expectedTailnet != "" && expectedTailnet != tailnet { 92 | w.WriteHeader(http.StatusForbidden) 93 | log.Printf("user is part of tailnet %s, wanted: %s", tailnet, url.QueryEscape(expectedTailnet)) 94 | return 95 | } 96 | 97 | h := w.Header() 98 | h.Set("Tailscale-Login", strings.Split(info.UserProfile.LoginName, "@")[0]) 99 | h.Set("Tailscale-User", info.UserProfile.LoginName) 100 | h.Set("Tailscale-Name", info.UserProfile.DisplayName) 101 | h.Set("Tailscale-Profile-Picture", info.UserProfile.ProfilePicURL) 102 | h.Set("Tailscale-Tailnet", tailnet) 103 | w.WriteHeader(http.StatusNoContent) 104 | }) 105 | 106 | ln, err := net.Listen(*listenProto, *listenAddr) 107 | if err != nil { 108 | log.Fatalf("can't listen on %s: %v", *listenAddr, err) 109 | } 110 | defer ln.Close() 111 | 112 | log.Printf("listening on %s", ln.Addr()) 113 | log.Fatal(http.Serve(ln, mux)) 114 | } 115 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= 2 | filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= 3 | github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= 4 | github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= 5 | github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= 6 | github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= 7 | github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= 8 | github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= 9 | github.com/cilium/ebpf v0.8.1 h1:bLSSEbBLqGPXxls55pGr5qWZaTqcmfDJHhou7t254ao= 10 | github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk= 11 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 12 | github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= 13 | github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= 14 | github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= 15 | github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= 16 | github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= 17 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 18 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 19 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 20 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 21 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 22 | github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU= 23 | github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= 24 | github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= 25 | github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= 26 | github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= 27 | github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= 28 | github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b h1:Yws7RV6kZr2O7PPdT+RkbSmmOponA8i/1DuGHe8BRsM= 29 | github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b/go.mod h1:TzDCVOZKUa79z6iXbbXqhtAflVgUKaFkZ21M5tK5tzY= 30 | github.com/jsimonetti/rtnetlink v1.3.3 h1:ycpm3z8XlAzmaacVRjdUT3x6MM1o3YBXsXc7DXSRNCE= 31 | github.com/jsimonetti/rtnetlink v1.3.3/go.mod h1:mW4xSP3wkiqWxHMlfG/gOufp3XnhAxu7EhfABmrWSh8= 32 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 33 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 34 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 35 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 36 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 37 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 38 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 39 | github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= 40 | github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= 41 | github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0= 42 | github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= 43 | github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= 44 | github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= 45 | github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= 46 | github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM= 47 | github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY= 48 | github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= 49 | github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= 50 | github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= 51 | github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= 52 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 53 | github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 h1:Ha8xCaq6ln1a+R91Km45Oq6lPXj2Mla6CRJYcuV2h1w= 54 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 55 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 56 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 57 | go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= 58 | go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA= 59 | go4.org/mem v0.0.0-20210711025021-927187094b94 h1:OAAkygi2Js191AJP1Ds42MhJRgeofeKGjuoUqNp1QC4= 60 | go4.org/mem v0.0.0-20210711025021-927187094b94/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= 61 | go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= 62 | go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= 63 | go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 h1:nJAwRlGWZZDOD+6wni9KVUNHMpHko/OnRwsrCYeAzPo= 64 | go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y= 65 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 h1:Tx9kY6yUkLge/pFG7IEMwDZy6CS2ajFc9TvQdPCW0uA= 66 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= 67 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= 68 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 69 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 70 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 71 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 72 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 73 | golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= 74 | golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 75 | golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= 76 | golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= 77 | golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= 78 | golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= 79 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 80 | golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= 81 | golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 82 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 83 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 84 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 85 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 86 | golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 87 | golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 88 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 89 | golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 h1:EN5+DfgmRMvRUrMGERW2gQl3Vc+Z7ZMnI/xdEpPSf0c= 90 | golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 91 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 92 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 93 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 94 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 95 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 96 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 97 | golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= 98 | golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 99 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 100 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 101 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 102 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 103 | golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 104 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 105 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 106 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 107 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 108 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 109 | golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 110 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 111 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 112 | golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 113 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= 114 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 115 | golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 116 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 117 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 118 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 119 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 120 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 121 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 122 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 123 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 124 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 125 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= 126 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 127 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 128 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 129 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 130 | golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= 131 | golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= 132 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 133 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 134 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 135 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 136 | golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= 137 | golang.zx2c4.com/wireguard v0.0.0-20210905140043-2ef39d47540c/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8= 138 | golang.zx2c4.com/wireguard/windows v0.4.10 h1:HmjzJnb+G4NCdX+sfjsQlsxGPuYaThxRbZUZFLyR0/s= 139 | golang.zx2c4.com/wireguard/windows v0.4.10/go.mod h1:v7w/8FC48tTBm1IzScDVPEEb0/GjLta+T0ybpP9UWRg= 140 | golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= 141 | golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= 142 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 143 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 144 | inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 h1:acCzuUSQ79tGsM/O50VRFySfMm19IoMKL+sZztZkCxw= 145 | inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6/go.mod h1:y3MGhcFMlh0KZPMuXXow8mpjxxAk3yoDNsp4cQz54i8= 146 | tailscale.com v1.24.2 h1:xNqEMKLHjqKwKUlggL2QEt1B+oit08w3SwZEIWCmqTg= 147 | tailscale.com v1.24.2/go.mod h1:/z/lF98LSt9CjpwVT6pHh5Vwb1NGsM5/ACI4cLMJfvY= 148 | tailscale.com v1.40.1 h1:NdIUKoD6DHXZY7SnMN85ERPbXZsx6ipiRa6odnrYZtU= 149 | tailscale.com v1.40.1/go.mod h1:j5vekUD4eLhLpHl/tNBps25strCOBXyiKUsdR1HhMq8= 150 | --------------------------------------------------------------------------------