├── .dockerignore ├── .github ├── update-nix-hashes.py └── workflows │ └── autorelease.yml ├── .gitignore ├── Dockerfile ├── README.md ├── cmd └── ts-proxyd │ └── main.go ├── container.nix ├── flake.lock ├── flake.nix ├── go.mod ├── go.sum ├── http.go ├── make_release ├── package.nix ├── proxy.go ├── renovate.json ├── shell.nix ├── tcp.go └── version.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | *.nix 3 | -------------------------------------------------------------------------------- /.github/update-nix-hashes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from pathlib import Path 3 | import re 4 | import subprocess 5 | 6 | 7 | PACKAGE_NIX = Path(__file__).parent.parent / "package.nix" 8 | 9 | EMPTY_HASH = "sha256:" + (64 * "0") 10 | 11 | OLD_HASH_RE = re.compile(r" vendorHash = \"(sha256[^\"]*)") 12 | NEW_HASH_FROM_LOGS_RE = re.compile(r"got: *(sha256[^$]*=)") 13 | 14 | 15 | subprocess.run(["git", "checkout", "HEAD", str(PACKAGE_NIX)]) 16 | 17 | original_text = PACKAGE_NIX.read_text() 18 | # print(original_text) 19 | 20 | 21 | findings = OLD_HASH_RE.findall(original_text) 22 | print('[DEBUG] findings old', findings) 23 | assert len(findings) == 1 24 | 25 | old_hash = findings[0].strip() 26 | 27 | PACKAGE_NIX.write_text(original_text.replace(old_hash, EMPTY_HASH)) 28 | 29 | drvPath = subprocess.run(['nix', 'eval', str(PACKAGE_NIX.parent) + "#default.drvPath", '--raw'], stdout=subprocess.PIPE).stdout.decode('utf-8') 30 | print('drvPath', drvPath) 31 | 32 | build_log = subprocess.run(['nix-store', '-r', drvPath], stderr=subprocess.PIPE).stderr.decode('utf-8') 33 | 34 | findings = NEW_HASH_FROM_LOGS_RE.findall(build_log) 35 | print('[DEBUG] findings new', findings) 36 | assert len(findings) == 1 37 | new_hash = findings[0].strip() 38 | PACKAGE_NIX.write_text(original_text.replace(old_hash, new_hash)) 39 | 40 | subprocess.run(["nix", "build", "-L", "--no-link", str(PACKAGE_NIX.parent)]) 41 | -------------------------------------------------------------------------------- /.github/workflows/autorelease.yml: -------------------------------------------------------------------------------- 1 | name: Autorelease 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - '*' 9 | workflow_dispatch: 10 | inputs: 11 | new_version: 12 | description: 'New tag version' 13 | default: 'patch' 14 | schedule: 15 | - cron: '0 2 * * 6' # saturday 2am 16 | jobs: 17 | autorelease: 18 | env: 19 | REGISTRY: ghcr.io 20 | IMAGE_NAME: ${{ github.repository }} 21 | USERNAME: ${{ github.actor }} 22 | runs-on: ubuntu-latest 23 | permissions: 24 | contents: write 25 | packages: write 26 | attestations: write 27 | id-token: write 28 | pull-requests: write 29 | steps: 30 | - name: Install Nix 31 | uses: cachix/install-nix-action@v31 32 | with: 33 | nix_path: nixpkgs=channel:nixos-unstable 34 | - name: Login to registry 35 | uses: docker/login-action@v3 36 | with: 37 | registry: ${{ env.REGISTRY }} 38 | username: ${{ env.USERNAME }} 39 | password: ${{ github.token }} 40 | 41 | - uses: actions/checkout@v4 42 | - name: Setup git config 43 | run: | 44 | git config user.name actions-bot 45 | git config user.email actions-bot@users.noreply.github.com 46 | 47 | - name: Update Nix flake 48 | run: nix flake update 49 | 50 | - name: Update Nix hashes 51 | run: ./.github/update-nix-hashes.py 52 | 53 | - name: Create Pull Request if there is new stuff from updaters 54 | uses: peter-evans/create-pull-request@v7 55 | id: pr_create 56 | with: 57 | commit-message: Updater script changes 58 | branch: updater-bot 59 | delete-branch: true 60 | title: "Updater: stuff changed" 61 | body: | 62 | Changes caused from update scripts 63 | 64 | - name: Stop if a pull request was created 65 | env: 66 | PR_NUMBER: ${{ steps.pr_create.outputs.pull-request-number }} 67 | run: | 68 | if [[ ! -z "$PR_NUMBER" ]]; then 69 | echo "The update scripts changed something and a PR was created. Giving up deploy." >> $GITHUB_STEP_SUMMARY 70 | exit 1 71 | fi 72 | - uses: actions/setup-go@v5.5.0 73 | with: 74 | go-version: '1.24' 75 | 76 | - name: Try to build it 77 | run: 'nix build .#' 78 | 79 | - run: mkdir build -p && ls && pwd 80 | - run: GOOS=windows GOARCH=amd64 go build -o build/ts-proxyd-windows-amd64.exe ./cmd/ts-proxyd 81 | - run: go build -o build/ts-proxyd-linux-amd64 ./cmd/ts-proxyd 82 | 83 | - name: Make release if everything looks right 84 | env: 85 | NEW_VERSION: ${{ github.event.inputs.new_version }} 86 | run: | 87 | if [[ ! -z "$NEW_VERSION" ]]; then 88 | NO_TAG=1 ./make_release "$NEW_VERSION" 89 | echo "New version: $(cat version.txt)" >> $GITHUB_STEP_SUMMARY 90 | echo "RELEASE_VERSION=$(cat version.txt)" >> $GITHUB_ENV 91 | fi 92 | 93 | - name: Create relase 94 | if: env.RELEASE_VERSION != '' 95 | id: release 96 | uses: elgohr/Github-Release-Action@v5 97 | env: 98 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 99 | with: 100 | tag: ${{ env.RELEASE_VERSION }} 101 | title: Release ${{ env.RELEASE_VERSION }} 102 | 103 | - uses: svenstaro/upload-release-action@v2 104 | if: env.RELEASE_VERSION != '' 105 | with: 106 | repo_token: ${{ secrets.GITHUB_TOKEN }} 107 | file: build/* 108 | tag: ${{ env.RELEASE_VERSION }} 109 | overwrite: true 110 | file_glob: true 111 | 112 | - name: "Build and publish container" 113 | env: 114 | TAG: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 115 | if: env.RELEASE_VERSION != '' 116 | run: | 117 | VERSION="$(cat version.txt)" 118 | docker build -t "$TAG:$VERSION" . 119 | docker tag "$TAG:$VERSION" "$TAG:latest" 120 | docker push "$TAG:$VERSION" 121 | docker push "$TAG:latest" 122 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # binary created with go build 2 | main 3 | 4 | # nix result link 5 | result* 6 | ts-proxyd 7 | build 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24-alpine AS build-env 2 | 3 | WORKDIR /go/src/ts-proxy 4 | 5 | COPY go.mod go.sum ./ 6 | 7 | RUN go mod download 8 | 9 | COPY . ./ 10 | 11 | ARG VERSION_LONG 12 | ENV VERSION_LONG=$VERSION_LONG 13 | 14 | ARG VERSION_GIT 15 | ENV VERSION_GIT=$VERSION_GIT 16 | 17 | RUN go build -v -o ts-proxyd ./cmd/ts-proxyd 18 | 19 | FROM alpine:3.22 20 | 21 | RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables 22 | 23 | COPY --from=build-env /go/src/ts-proxy/ts-proxyd /usr/local/bin 24 | 25 | ENTRYPOINT [ "/usr/local/bin/ts-proxyd" ] 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ts-proxy 2 | 3 | Simple proxy program to allow exposing individual services to a Tailnet, and 4 | even to the Internet using Tailscale Funnel. 5 | 6 | Unfortunately, the Tailscale daemon only allows exposing services using the 7 | current node domain, and you can't spawn (so far) nodes for services. With this 8 | you can! 9 | 10 | On first run for one service, you will have to authenticate the service using 11 | your Tailscale account. The authentication can be either done passing an 12 | authentication token through the `TS_AUTHKEY` environment or by reading the 13 | startup logs until you find the authentication link. After authentication, 14 | Tailscale will store the certificates and credentials to the location specified 15 | by the `-s` flag so subsequent runs will not require reauthentication and up-to-date authorization tokens. 16 | 17 | As the [main build of Tailscale](https://tailscale.com/s/serve-headers), you 18 | can get information about the user accessing the service using the following 19 | headers that get forwarded to the upstream service: 20 | - `Tailscale-User-Login` 21 | - `Tailscale-User-Name` 22 | - `Tailscale-User-Profile-Pic` 23 | 24 | And yeah, you can use Tailscale as a single sign on and have a public facing 25 | version! It's as safe and stable as 26 | [tclip](https://github.com/tailscale-dev/tclip) is because this proxy uses the 27 | exact same primitives. 28 | 29 | > [!WARNING] 30 | > You can count on the headers sent by ts-proxy as long as you follow the following conditions: 31 | > - Anything that changes the headers name representation such as Apache with PHP could be cheated 32 | > by passing the header TAILSCALE_USER_LOGIN, for example. 33 | > 34 | > - If some users can access your actual service directly without passing the traffic through ts-proxy 35 | they can change all the headers they want, including authentication ones. 36 | > 37 | > - If you don't use the header authentication for anything in a given service these issues will not be a problem for that service. 38 | 39 | 40 | ## Usage 41 | 42 | ``` 43 | Usage of ./ts-proxyd: 44 | -addr string 45 | Port to listen (default ":443") 46 | -f Enable tailscale funnel 47 | -h string 48 | Where to forward the connection 49 | -n string 50 | Hostname in tailscale devices list 51 | -s string 52 | State directory 53 | ``` 54 | 55 | ## Release schedule 56 | Version structure example: 0.7.10 57 | - 0: major 58 | - 7: minor 59 | - 10: patch 60 | 61 | Each week an automatic PR is sent to update the package dependencies. For each update there will be a patch release. 62 | 63 | Bug fixes would be shipped in patch releases. 64 | 65 | Anything that has breaking changes by changing something in this repository will be released as a minor release. 66 | 67 | No plans for bumping the major versions yet. 68 | 69 | ## Next steps 70 | - [ ] A way to expose a folder, maybe using single page application patterns, instead of only ports. 71 | - [ ] Experiments around exposing many nodes using only one process and a TOML, or YAML, config file. 72 | -------------------------------------------------------------------------------- /cmd/ts-proxyd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | 7 | "github.com/davecgh/go-spew/spew" 8 | tsproxy "github.com/lucasew/ts-proxy" 9 | ) 10 | 11 | var options tsproxy.TailscaleProxyServerOptions 12 | 13 | func init() { 14 | var err error 15 | flag.StringVar(&options.Network, "net", "tcp", "Network, for net.Dial") 16 | flag.StringVar(&options.Address, "address", "", "Where to forward the connection") 17 | flag.StringVar(&options.Hostname, "n", "", "Hostname in tailscale devices list") 18 | flag.BoolVar(&options.EnableFunnel, "f", false, "Enable tailscale funnel") 19 | flag.BoolVar(&options.EnableTLS, "t", false, "Enable HTTPS/TLS") 20 | flag.StringVar(&options.StateDir, "s", "", "State directory") 21 | flag.StringVar(&options.Listen, "listen", "", "Port to listen") 22 | flag.BoolVar(&options.EnableHTTP, "raw", false, "Disable HTTP handling") 23 | flag.Parse() 24 | options.EnableHTTP = !options.EnableHTTP 25 | if options.Listen == "" && options.EnableHTTP { 26 | if options.EnableFunnel || options.EnableTLS { 27 | options.Listen = ":443" 28 | } else { 29 | options.Listen = ":80" 30 | } 31 | } 32 | spew.Dump(options) 33 | if options.Listen == "" { 34 | panic("-listen not defined") 35 | } 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | } 40 | 41 | func main() { 42 | server, err := tsproxy.NewTailscaleProxyServer(options) 43 | if err != nil { 44 | panic(err) 45 | } 46 | server.Run() 47 | } 48 | -------------------------------------------------------------------------------- /container.nix: -------------------------------------------------------------------------------- 1 | { dockerTools 2 | , cacert 3 | , callPackage 4 | , lib 5 | , self 6 | }: 7 | 8 | dockerTools.streamLayeredImage { 9 | name = "ts-proxy"; 10 | tag = "${builtins.readFile ./version.txt}-${self.shortRev or self.dirtyShortRev}"; 11 | maxLayers = 2; 12 | 13 | contents = [ 14 | dockerTools.binSh 15 | (dockerTools.fakeNss.override { 16 | extraPasswdLines = ["user:x:1000:1000:new user:/tmp:/bin/sh"]; 17 | extraGroupLines = ["user:x:1000:"]; 18 | }) 19 | ]; 20 | 21 | extraCommands = '' 22 | mkdir -m777 -p tmp etc dev/shm 23 | ''; 24 | 25 | uid = 1000; 26 | gid = 1000; 27 | uname = "user"; 28 | gname = "user"; 29 | 30 | config = { 31 | Entrypoint = [ 32 | (lib.getExe (callPackage ./package.nix {inherit self;})) 33 | ]; 34 | User = "user"; 35 | Env = [ 36 | "SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt" 37 | "HOME=/tmp" 38 | "LANGUAGE=en_US" 39 | "UID=1000" 40 | "GID=1000" 41 | "TZ=UTC" 42 | ]; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1749401433, 24 | "narHash": "sha256-HXIQzULIG/MEUW2Q/Ss47oE3QrjxvpUX7gUl4Xp6lnc=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "08fcb0dcb59df0344652b38ea6326a2d8271baff", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "id": "nixpkgs", 32 | "type": "indirect" 33 | } 34 | }, 35 | "root": { 36 | "inputs": { 37 | "flake-utils": "flake-utils", 38 | "nixpkgs": "nixpkgs" 39 | } 40 | }, 41 | "systems": { 42 | "locked": { 43 | "lastModified": 1681028828, 44 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 45 | "owner": "nix-systems", 46 | "repo": "default", 47 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 48 | "type": "github" 49 | }, 50 | "original": { 51 | "owner": "nix-systems", 52 | "repo": "default", 53 | "type": "github" 54 | } 55 | } 56 | }, 57 | "root": "root", 58 | "version": 7 59 | } 60 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A simple reverse proxy that appears as a additional node in your Tailscale network"; 3 | 4 | inputs = { 5 | nixpkgs.url = "nixpkgs"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | }; 8 | 9 | outputs = { nixpkgs, flake-utils, ... }@self: 10 | flake-utils.lib.eachDefaultSystem (system: let 11 | pkgs = import nixpkgs { inherit system; }; 12 | in { 13 | packages = { 14 | default = pkgs.python3Packages.callPackage ./package.nix { inherit self; }; 15 | container = pkgs.python3Packages.callPackage ./container.nix { inherit self; }; 16 | }; 17 | devShells.default = pkgs.mkShell { 18 | buildInputs = with pkgs; [ 19 | gopls 20 | go 21 | ]; 22 | }; 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lucasew/ts-proxy 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.4 6 | 7 | require tailscale.com v1.84.2 8 | 9 | require ( 10 | github.com/akutz/memconn v0.1.0 // indirect 11 | github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect 12 | github.com/aws/aws-sdk-go-v2 v1.36.0 // indirect 13 | github.com/aws/aws-sdk-go-v2/config v1.29.5 // indirect 14 | github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 // indirect 15 | github.com/bits-and-blooms/bitset v1.13.0 // indirect 16 | github.com/coder/websocket v1.8.12 // indirect 17 | github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect 18 | github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect 19 | github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect 20 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 21 | github.com/gaissmai/bart v0.18.0 // indirect 22 | github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect 23 | github.com/go-ole/go-ole v1.3.0 // indirect 24 | github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect 25 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 26 | github.com/google/go-cmp v0.6.0 // indirect 27 | github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect 28 | github.com/google/uuid v1.6.0 // indirect 29 | github.com/gorilla/securecookie v1.1.2 // indirect 30 | github.com/hdevalence/ed25519consensus v0.2.0 // indirect 31 | github.com/illarion/gonotify/v2 v2.0.3 // indirect 32 | github.com/illarion/gonotify/v3 v3.0.2 // indirect 33 | github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect 34 | github.com/jsimonetti/rtnetlink v1.4.0 // indirect 35 | github.com/klauspost/compress v1.17.11 // indirect 36 | github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect 37 | github.com/mdlayher/genetlink v1.3.2 // indirect 38 | github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect 39 | github.com/mdlayher/sdnotify v1.0.0 // indirect 40 | github.com/miekg/dns v1.1.58 // indirect 41 | github.com/mitchellh/go-ps v1.0.0 // indirect 42 | github.com/prometheus-community/pro-bing v0.4.0 // indirect 43 | github.com/safchain/ethtool v0.3.0 // indirect 44 | github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect 45 | github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869 // indirect 46 | github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect 47 | github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect 48 | github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect 49 | github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect 50 | github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect 51 | github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251 // indirect 52 | github.com/vishvananda/netns v0.0.4 // indirect 53 | go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect 54 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect 55 | golang.org/x/crypto v0.37.0 // indirect 56 | golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect 57 | golang.org/x/mod v0.23.0 // indirect 58 | golang.org/x/net v0.36.0 // indirect 59 | golang.org/x/sync v0.13.0 // indirect 60 | golang.org/x/sys v0.32.0 // indirect 61 | golang.org/x/term v0.31.0 // indirect 62 | golang.org/x/time v0.10.0 // indirect 63 | golang.org/x/tools v0.30.0 // indirect 64 | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect 65 | golang.zx2c4.com/wireguard/windows v0.5.3 // indirect 66 | gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 // indirect 67 | ) 68 | 69 | require ( 70 | filippo.io/edwards25519 v1.1.0 // indirect 71 | github.com/aws/aws-sdk-go-v2/credentials v1.17.58 // indirect 72 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 // indirect 73 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 // indirect 74 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 // indirect 75 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect 76 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect 77 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 // indirect 78 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 // indirect 79 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 // indirect 80 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 // indirect 81 | github.com/aws/smithy-go v1.22.2 // indirect 82 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc 83 | github.com/google/btree v1.1.2 // indirect 84 | github.com/gorilla/csrf v1.7.3 // indirect 85 | github.com/jmespath/go-jmespath v0.4.0 // indirect 86 | github.com/mdlayher/socket v0.5.0 // indirect 87 | github.com/pierrec/lz4/v4 v4.1.21 // indirect 88 | github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect 89 | github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect 90 | github.com/x448/float16 v0.8.4 // indirect 91 | golang.org/x/text v0.24.0 // indirect 92 | ) 93 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 2 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 3 | filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc= 4 | filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA= 5 | github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= 6 | github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 7 | github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= 8 | github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= 9 | github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= 10 | github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= 11 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= 12 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= 13 | github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= 14 | github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= 15 | github.com/aws/aws-sdk-go-v2 v1.36.0 h1:b1wM5CcE65Ujwn565qcwgtOTT1aT4ADOHHgglKjG7fk= 16 | github.com/aws/aws-sdk-go-v2 v1.36.0/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM= 17 | github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw= 18 | github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU= 19 | github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k= 20 | github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg= 21 | github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= 22 | github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= 23 | github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y= 24 | github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A= 25 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= 26 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= 27 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPdVAqZ6A+LheHWb+mHbNOq8= 28 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY= 29 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= 30 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= 31 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 h1:lWm9ucLSRFiI4dQQafLrEOmEDGry3Swrz0BIRdiHJqQ= 32 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31/go.mod h1:Huu6GG0YTfbPphQkDSo4dEGmQRTKb9k9G7RdtyQWxuI= 33 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= 34 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= 35 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 h1:ACxDklUKKXb48+eg5ROZXi1vDgfMyfIA/WyvqHcHI0o= 36 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31/go.mod h1:yadnfsDwqXeVaohbGc/RaD287PuyRw2wugkh5ZL2J6k= 37 | github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= 38 | github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= 39 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk= 40 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= 41 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= 42 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= 43 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= 44 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY= 45 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= 46 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= 47 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 h1:O+8vD2rGjfihBewr5bT+QUfYUHIxCVgG61LHoT59shM= 48 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12/go.mod h1:usVdWJaosa66NMvmCrr08NcWDBRv4E6+YFG2pUdw1Lk= 49 | github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE= 50 | github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM= 51 | github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= 52 | github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= 53 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 h1:c5WJ3iHz7rLIgArznb3JCSQT3uUMiz9DLZhIX+1G8ok= 54 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs= 55 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= 56 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= 57 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 h1:f1L/JtUkVODD+k1+IiSJUUv8A++2qVr+Xvb3xWXETMU= 58 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U= 59 | github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= 60 | github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= 61 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 h1:3LXNnmtH3TURctC23hnC0p/39Q5gre3FI7BNOiDcVWc= 62 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.13/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w= 63 | github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= 64 | github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= 65 | github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= 66 | github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= 67 | github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= 68 | github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 69 | github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= 70 | github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= 71 | github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= 72 | github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= 73 | github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= 74 | github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= 75 | github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= 76 | github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= 77 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 78 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 79 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 80 | github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk= 81 | github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ= 82 | github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= 83 | github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= 84 | github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= 85 | github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= 86 | github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI= 87 | github.com/dsnet/try v0.0.3/go.mod h1:WBM8tRpUmnXXhY1U6/S8dt6UWdHTQ7y8A5YSkRCkq40= 88 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 89 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 90 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 91 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 92 | github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= 93 | github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= 94 | github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo= 95 | github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY= 96 | github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= 97 | github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= 98 | github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84= 99 | github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= 100 | github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY= 101 | github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= 102 | github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= 103 | github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= 104 | github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= 105 | github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= 106 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 107 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 108 | github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= 109 | github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 110 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 111 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 112 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 113 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 114 | github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= 115 | github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= 116 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 117 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 118 | github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M= 119 | github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= 120 | github.com/gorilla/csrf v1.7.3 h1:BHWt6FTLZAb2HtWT5KDBf6qgpZzvtbp9QWDRKZMXJC0= 121 | github.com/gorilla/csrf v1.7.3/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= 122 | github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= 123 | github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 124 | github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= 125 | github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= 126 | github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A= 127 | github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= 128 | github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk= 129 | github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U= 130 | github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= 131 | github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= 132 | github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g= 133 | github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= 134 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 135 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 136 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 137 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 138 | github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= 139 | github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= 140 | github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= 141 | github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 142 | github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= 143 | github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= 144 | github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= 145 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 146 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 147 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 148 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 149 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 150 | github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= 151 | github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= 152 | github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= 153 | github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= 154 | github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= 155 | github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= 156 | github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= 157 | github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= 158 | github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= 159 | github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= 160 | github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= 161 | github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= 162 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 163 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 164 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= 165 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 166 | github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= 167 | github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 168 | github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= 169 | github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= 170 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 171 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 172 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 173 | github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4= 174 | github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4= 175 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 176 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 177 | github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= 178 | github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= 179 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 180 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 181 | github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= 182 | github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= 183 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 184 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 185 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 186 | github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ= 187 | github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4= 188 | github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4= 189 | github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= 190 | github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw= 191 | github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= 192 | github.com/tailscale/golang-x-crypto v0.0.0-20250218230618-9a281fd8faca/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= 193 | github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= 194 | github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= 195 | github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= 196 | github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= 197 | github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= 198 | github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= 199 | github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= 200 | github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= 201 | github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= 202 | github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= 203 | github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= 204 | github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M= 205 | github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y= 206 | github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19 h1:BcEJP2ewTIK2ZCsqgl6YGpuO6+oKqqag5HHb7ehljKw= 207 | github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= 208 | github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251 h1:h/41LFTrwMxB9Xvvug0kRdQCU5TlV1+pAMQw0ZtDE3U= 209 | github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= 210 | github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek= 211 | github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= 212 | github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= 213 | github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= 214 | github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs= 215 | github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI= 216 | github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= 217 | github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= 218 | github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= 219 | github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= 220 | github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 221 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 222 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 223 | go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= 224 | go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= 225 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= 226 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= 227 | golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= 228 | golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= 229 | golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= 230 | golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= 231 | golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= 232 | golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 233 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= 234 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= 235 | golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= 236 | golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= 237 | golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= 238 | golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 239 | golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= 240 | golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= 241 | golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= 242 | golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 243 | golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= 244 | golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 245 | golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= 246 | golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= 247 | golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= 248 | golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= 249 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 250 | golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= 251 | golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 252 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 253 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 254 | golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 255 | golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 256 | golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 257 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 258 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 259 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 260 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 261 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 262 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 263 | golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= 264 | golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= 265 | golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= 266 | golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 267 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 268 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 269 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 270 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 271 | golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= 272 | golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 273 | golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= 274 | golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 275 | golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= 276 | golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= 277 | golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= 278 | golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= 279 | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= 280 | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= 281 | golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= 282 | golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= 283 | google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= 284 | google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 285 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 286 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 287 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 288 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 289 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 290 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 291 | gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 h1:TU8z2Lh3Bbq77w0t1eG8yRlLcNHzZu3x6mhoH2Mk0c8= 292 | gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU= 293 | gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= 294 | gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= 295 | honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= 296 | honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= 297 | howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= 298 | howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= 299 | software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= 300 | software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= 301 | tailscale.com v1.80.3 h1:uGLWZdl61YbhvhoU6qdnHPF7zuuqGGRaTfbECur035Y= 302 | tailscale.com v1.80.3/go.mod h1:HTOFVeo5RY0qBl5Uy+LXHwgp0PLXgVSfgqWI34gSrPA= 303 | tailscale.com v1.82.5 h1:p5owmyPoPM1tFVHR3LjquFuLfpZLzafvhe5kjVavHtE= 304 | tailscale.com v1.82.5/go.mod h1:iU6kohVzG+bP0/5XjqBAnW8/6nSG/Du++bO+x7VJZD0= 305 | tailscale.com v1.84.0 h1:WzelL3/TXAAN+Vv5UyK0n0JCOL9n0qpjRL4tjVEA1Ok= 306 | tailscale.com v1.84.0/go.mod h1:6/S63NMAhmncYT/1zIPDJkvCuZwMw+JnUuOfSPNazpo= 307 | tailscale.com v1.84.1 h1:xtuiYeAIUR+dRztPzzqUsjj+Fv/06vz28zoFaP1k/Os= 308 | tailscale.com v1.84.1/go.mod h1:6/S63NMAhmncYT/1zIPDJkvCuZwMw+JnUuOfSPNazpo= 309 | tailscale.com v1.84.2 h1:v6aM4RWUgYiV52LRAx6ET+dlGnvO/5lnqPXb7/pMnR0= 310 | tailscale.com v1.84.2/go.mod h1:6/S63NMAhmncYT/1zIPDJkvCuZwMw+JnUuOfSPNazpo= 311 | -------------------------------------------------------------------------------- /http.go: -------------------------------------------------------------------------------- 1 | package tsproxy 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "net/http" 7 | "net/http/httputil" 8 | "net/url" 9 | 10 | "github.com/davecgh/go-spew/spew" 11 | ) 12 | 13 | func init() { 14 | _ = spew.Dump 15 | } 16 | 17 | type TailscaleHTTPProxyServer struct { 18 | server *TailscaleProxyServer 19 | proxy *httputil.ReverseProxy 20 | } 21 | 22 | func NewTailscaleHTTPProxyServer(server *TailscaleProxyServer) (Server, error) { 23 | u := &url.URL{ 24 | Scheme: "http", 25 | Host: server.Hostname(), 26 | } 27 | proxy := httputil.NewSingleHostReverseProxy(u) 28 | proxy.Transport = &http.Transport{ 29 | Dial: server.Dial, 30 | } 31 | return &TailscaleHTTPProxyServer{ 32 | server: server, 33 | proxy: proxy, 34 | }, nil 35 | } 36 | 37 | func (tps *TailscaleHTTPProxyServer) Serve(l net.Listener) error { 38 | server := http.Server{Handler: tps} 39 | return server.Serve(l) 40 | } 41 | 42 | func (tps *TailscaleHTTPProxyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 43 | userInfo, err := tps.server.WhoIs(r.Context(), r.RemoteAddr) 44 | if err != nil { 45 | log.Printf("error/http/ts-auth: %s", err.Error()) 46 | w.WriteHeader(500) 47 | return 48 | } 49 | if r.URL.Hostname() != "" && r.URL.Hostname() != tps.server.Hostname() { 50 | destinationURL := new(url.URL) 51 | *destinationURL = *r.URL 52 | destinationURL.Host = tps.server.Hostname() + tps.server.options.Listen 53 | if tps.server.options.EnableTLS { 54 | destinationURL.Scheme = "https" 55 | } else { 56 | destinationURL.Scheme = "http" 57 | } 58 | log.Printf("redirect: '%s' -> '%s'", r.URL.String(), destinationURL.String()) 59 | http.Redirect(w, r, destinationURL.String(), http.StatusMovedPermanently) 60 | return 61 | } 62 | log.Printf("%s %s %s %s", r.Method, userInfo.UserProfile.LoginName, r.Host, r.URL.String()) 63 | r.Header.Set("Tailscale-User-Login", userInfo.UserProfile.LoginName) 64 | r.Header.Set("Tailscale-User-Name", userInfo.UserProfile.DisplayName) 65 | r.Header.Set("Tailscale-User-Profile-Pic", userInfo.UserProfile.ProfilePicURL) 66 | r.Header.Set("Tailscale-Headers-Info", "https://tailscale.com/s/serve-headers") 67 | tps.proxy.ServeHTTP(w, r) 68 | } 69 | -------------------------------------------------------------------------------- /make_release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# == 1 ]; then 4 | VERSION="$1"; shift 5 | else 6 | echo ./make_release versao_nova >&2 7 | exit 1 8 | fi 9 | 10 | CURRENT_VERSION=($(cat version.txt | sed 's;\.; ;g')) 11 | 12 | case "$VERSION" in 13 | patch) 14 | VERSION="${CURRENT_VERSION[0]}.${CURRENT_VERSION[1]}.$((${CURRENT_VERSION[2]}+1))" 15 | ;; 16 | minor) 17 | VERSION="${CURRENT_VERSION[0]}.$((${CURRENT_VERSION[1]}+1)).0" 18 | ;; 19 | major) 20 | VERSION="$((${CURRENT_VERSION[0]}+1)).0.0" 21 | ;; 22 | esac 23 | 24 | # echo new version: $VERSION 25 | # exit 0 26 | printf "%s" "$VERSION" > version.txt 27 | 28 | git add -A 29 | git commit -sm "bump to $VERSION" 30 | if [[ ! -v NO_TAG ]]; then 31 | git tag "$VERSION" 32 | git push --tag 33 | fi 34 | git push 35 | -------------------------------------------------------------------------------- /package.nix: -------------------------------------------------------------------------------- 1 | { buildGo124Module 2 | , self 3 | , lib 4 | }: 5 | buildGo124Module { 6 | pname = "ts-proxy"; 7 | version = "${builtins.readFile ./version.txt}-${self.shortRev or self.dirtyShortRev or "rev"}"; 8 | 9 | src = ./.; 10 | 11 | env.CGO_ENABLED = 0; 12 | 13 | # vendorHash = "sha256:${lib.fakeSha256}"; 14 | vendorHash = "sha256-KLtqMWFL75ZpzTD1MP/v1C/GkpCQ1nJeyQSOS8kyxP8="; 15 | 16 | postConfigure = '' 17 | # chmod -R +w vendor/gvisor.dev/gvisor #/pkg/refs/refs_template.go 18 | # rm vendor/gvisor.dev/gvisor/pkg/refs/refs_template.go 19 | # substituteInPlace vendor/gvisor.dev/gvisor/pkg/refs/refs_template.go \ 20 | # --replace refs_template refs 21 | ''; 22 | 23 | meta.mainProgram = "ts-proxyd"; 24 | } 25 | -------------------------------------------------------------------------------- /proxy.go: -------------------------------------------------------------------------------- 1 | package tsproxy 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "log" 7 | "net" 8 | 9 | "os" 10 | 11 | "tailscale.com/client/tailscale/apitype" 12 | "tailscale.com/tsnet" 13 | ) 14 | 15 | var ( 16 | ErrInvalidUpstream = errors.New("invalid upstream") 17 | ) 18 | 19 | type Server interface { 20 | Serve(ln net.Listener) error 21 | } 22 | 23 | type ListenerFunction func(network string, addr string) (net.Listener, error) 24 | 25 | type TailscaleProxyServer struct { 26 | ctx context.Context 27 | cancel func() 28 | options TailscaleProxyServerOptions 29 | server *tsnet.Server 30 | } 31 | 32 | type TailscaleProxyServerOptions struct { 33 | // context 34 | Context context.Context 35 | // node name in tailscale panel 36 | Hostname string 37 | // wether to enable Tailscale Funnel, will crash if no permissions 38 | EnableFunnel bool 39 | // wether to enable provisioning of TLS Certificates for HTTPS 40 | EnableTLS bool 41 | // wether to enable HTTP proxy logic 42 | EnableHTTP bool 43 | // where to store tailscale data 44 | StateDir string 45 | // protocol to listen, passed to net.Dial 46 | Network string 47 | // where to forward requests 48 | Address string 49 | // address to bind the server, passed to net.Dial 50 | Listen string 51 | } 52 | 53 | func NewTailscaleProxyServer(options TailscaleProxyServerOptions) (*TailscaleProxyServer, error) { 54 | if options.Context == nil { 55 | options.Context = context.Background() 56 | } 57 | ctx, cancel := context.WithCancel(options.Context) 58 | s := new(tsnet.Server) 59 | if options.Hostname == "" { 60 | options.Hostname = "tsproxy" 61 | } 62 | s.Hostname = options.Hostname 63 | if options.Address == "" { 64 | return nil, ErrInvalidUpstream 65 | } 66 | if options.StateDir != "" { 67 | err := os.MkdirAll(options.StateDir, 0700) 68 | if err != nil { 69 | return nil, err 70 | } 71 | s.Dir = options.StateDir 72 | } 73 | return &TailscaleProxyServer{ 74 | ctx: ctx, 75 | cancel: cancel, 76 | options: options, 77 | server: s, 78 | }, nil 79 | } 80 | 81 | func (tps *TailscaleProxyServer) listenFunnel(network string, addr string) (net.Listener, error) { 82 | return tps.server.ListenFunnel(network, addr) 83 | } 84 | 85 | func (tps *TailscaleProxyServer) Hostname() string { 86 | for _, domain := range tps.server.CertDomains() { 87 | return domain 88 | } 89 | return tps.options.Hostname 90 | } 91 | 92 | func (tps *TailscaleProxyServer) GetListenerFunction() ListenerFunction { 93 | if tps.options.EnableFunnel { 94 | return tps.listenFunnel 95 | } 96 | if tps.options.EnableTLS { 97 | return tps.server.ListenTLS 98 | } 99 | return tps.server.Listen 100 | } 101 | 102 | func (tps *TailscaleProxyServer) GetListener() (net.Listener, error) { 103 | return tps.GetListenerFunction()("tcp", tps.options.Listen) 104 | } 105 | 106 | func (tps *TailscaleProxyServer) Dial(network string, addr string) (net.Conn, error) { 107 | dialNetwork := tps.options.Network 108 | dialHost := tps.options.Address 109 | return net.Dial(dialNetwork, dialHost) 110 | } 111 | 112 | func (tps *TailscaleProxyServer) WhoIs(ctx context.Context, remoteAddr string) (*apitype.WhoIsResponse, error) { 113 | lc, err := tps.server.LocalClient() 114 | if err != nil { 115 | return nil, err 116 | } 117 | return lc.WhoIs(ctx, remoteAddr) 118 | } 119 | 120 | func (tps *TailscaleProxyServer) handleError(err error) bool { 121 | if err != nil { 122 | log.Printf("FATAL ERROR: %s\n", err.Error()) 123 | tps.cancel() 124 | } 125 | return err != nil 126 | } 127 | 128 | func (tps *TailscaleProxyServer) Run() { 129 | ln, err := tps.GetListener() 130 | if tps.handleError(err) { 131 | return 132 | } 133 | defer ln.Close() 134 | server := NewTailscaleTCPProxyServer(tps) 135 | if tps.options.EnableHTTP { 136 | server, err = NewTailscaleHTTPProxyServer(tps) 137 | if tps.handleError(err) { 138 | return 139 | } 140 | } 141 | server.Serve(ln) 142 | } 143 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} }: 2 | pkgs.mkShell { 3 | buildInputs = with pkgs; [ 4 | go_1_24 5 | gopls 6 | python3 7 | ]; 8 | } 9 | -------------------------------------------------------------------------------- /tcp.go: -------------------------------------------------------------------------------- 1 | package tsproxy 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "net" 7 | "sync" 8 | ) 9 | 10 | type TailscaleTCPProxyServer struct { 11 | server *TailscaleProxyServer 12 | } 13 | 14 | func NewTailscaleTCPProxyServer(server *TailscaleProxyServer) Server { 15 | return &TailscaleTCPProxyServer{ 16 | server: server, 17 | } 18 | } 19 | 20 | func (tps *TailscaleTCPProxyServer) Serve(ln net.Listener) error { 21 | for { 22 | select { 23 | case <-tps.server.ctx.Done(): 24 | break 25 | default: 26 | conn, err := ln.Accept() 27 | if err != nil { 28 | log.Printf("error/accept: %s", err.Error()) 29 | continue 30 | } 31 | log.Printf("got tcp conn") 32 | go handleTCPConn(tps.server, conn, nil) 33 | } 34 | } 35 | } 36 | 37 | var bufferPool = sync.Pool{ 38 | New: func() interface{} { 39 | // TODO maybe different buffer size? 40 | // benchmark pls 41 | return make([]byte, 1<<15) 42 | }, 43 | } 44 | 45 | func handleTCPConn(server *TailscaleProxyServer, c1 net.Conn, c2 net.Conn) { 46 | var err error 47 | if c2 == nil { 48 | c2, err = server.Dial("whatever", "whatever") 49 | if err != nil { 50 | log.Print(err) 51 | c1.Close() 52 | log.Printf("disconnected %v", c1.RemoteAddr()) 53 | return 54 | } 55 | 56 | } 57 | first := make(chan<- struct{}, 1) 58 | cp := func(dst net.Conn, src net.Conn) { 59 | buf := bufferPool.Get().([]byte) 60 | defer bufferPool.Put(buf) 61 | // TODO use splice on linux 62 | // TODO needs some timeout to prevent torshammer ddos 63 | _, err := io.CopyBuffer(dst, src, buf) 64 | select { 65 | case first <- struct{}{}: 66 | if err != nil { 67 | log.Print(err) 68 | } 69 | dst.Close() 70 | src.Close() 71 | log.Printf("disconnected %v", c1.RemoteAddr()) 72 | default: 73 | } 74 | } 75 | go cp(c1, c2) 76 | cp(c2, c1) 77 | } 78 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 0.8.16 --------------------------------------------------------------------------------