diff --git a/.github/workflows/controlplane.yaml b/.github/workflows/controlplane.yaml index 60663acd1..5241de3de 100644 --- a/.github/workflows/controlplane.yaml +++ b/.github/workflows/controlplane.yaml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6 - - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # ratchet:jdx/mise-action@v3 + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # ratchet:jdx/mise-action@v4 with: cache: false - run: mise run ${{ matrix.mise_task }} @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6 - - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # ratchet:jdx/mise-action@v3 + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # ratchet:jdx/mise-action@v4 - name: "Build controlplane" run: | @@ -75,7 +75,6 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6 - - name: Get Go version from go.mod id: go-version run: echo "version=$(awk '/^go /{print $2}' go.mod)" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/naisdevice.yaml b/.github/workflows/naisdevice.yaml index 3db85c1ae..6b5dd3e51 100644 --- a/.github/workflows/naisdevice.yaml +++ b/.github/workflows/naisdevice.yaml @@ -2,7 +2,7 @@ name: Naisdevice on: pull_request: - types: [opened, reopened, synchronize] + types: [opened, reopened, synchronize, labeled] push: branches: [main] paths: @@ -30,8 +30,9 @@ on: - "pkg/pb/**" env: + PRE_RELEASE: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'pre-release') && 'true' || 'false' }} # some mise tasks use this to determine how they package/sign stuff. - RELEASE: ${{ (github.ref == 'refs/heads/main' && github.actor != 'dependabot[bot]') && 'true' || 'false' }} + RELEASE: ${{ ((github.ref == 'refs/heads/main' && github.actor != 'dependabot[bot]') || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'pre-release'))) && 'true' || 'false' }} concurrency: group: ${{ github.ref }} @@ -50,14 +51,18 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6 with: fetch-depth: 0 - - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # ratchet:jdx/mise-action@v3 + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # ratchet:jdx/mise-action@v4 - id: generate run: mise run ci:release-info env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PRE_RELEASE: ${{ env.PRE_RELEASE }} + PR_NUMBER: ${{ github.event.pull_request.number }} outputs: version: ${{ steps.generate.outputs.version }} changelog: ${{ steps.generate.outputs.changelog }} + pre_release: ${{ env.PRE_RELEASE }} checks: strategy: @@ -76,7 +81,7 @@ jobs: contents: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6 - - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # ratchet:jdx/mise-action@v3 + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # ratchet:jdx/mise-action@v4 - run: mise run ${{ matrix.mise_task }} builds: @@ -109,7 +114,7 @@ jobs: OUTFILE: ./release_artifacts/naisdevice${{ matrix.gotags == 'tenant' && '-tenant' || '' }}_${{ matrix.platform.os }}_${{ matrix.arch }}.${{ matrix.platform.ext }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6 - - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # ratchet:jdx/mise-action@v3 + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # ratchet:jdx/mise-action@v4 - if: matrix.platform.os == 'windows' run: sudo apt-get update && sudo apt-get install --yes nsis osslsigncode - if: matrix.platform.os == 'macos' @@ -129,34 +134,83 @@ jobs: run: | mkdir -p "$(dirname $OUTFILE)" mise run "package:${{ matrix.platform.os }}" - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # ratchet:actions/upload-artifact@v5 + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # ratchet:actions/upload-artifact@v7 with: name: installer-${{ matrix.platform.os }}-${{ matrix.arch }}-${{ matrix.gotags || 'nav' }} path: ${{ env.OUTFILE }} + smoke-tests: + name: smoke test ${{ matrix.artifact }} + needs: [builds] + strategy: + fail-fast: false + matrix: + include: + - os: macos + runner: macos-latest + artifact: installer-macos-arm64-nav + installer_glob: "*.pkg" + - os: macos + runner: macos-latest + artifact: installer-macos-arm64-tenant + installer_glob: "*.pkg" + - os: linux + runner: ubuntu-latest + artifact: installer-linux-amd64-nav + installer_glob: "*.deb" + - os: linux + runner: ubuntu-latest + artifact: installer-linux-amd64-tenant + installer_glob: "*.deb" + - os: windows + runner: windows-latest + artifact: installer-windows-amd64-nav + installer_glob: "*.exe" + - os: windows + runner: windows-latest + artifact: installer-windows-amd64-tenant + installer_glob: "*.exe" + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6 + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # ratchet:jdx/mise-action@v4 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # ratchet:actions/download-artifact@v8 + with: + name: ${{ matrix.artifact }} + path: ./downloaded-artifact/ + - name: run smoke test + shell: bash + run: mise run smoke-test:${{ matrix.os }} ./downloaded-artifact/${{ matrix.installer_glob }} + # Used by GitHub to determine if all checks/builds have passed branch-protection-checkpoint: - needs: [checks, builds] + needs: [checks, builds, smoke-tests] if: ${{ always() }} runs-on: ubuntu-latest steps: - - if: ${{ needs.checks.result != 'success' || needs.builds.result != 'success' }} + - if: ${{ needs.checks.result != 'success' || needs.builds.result != 'success' || needs.smoke-tests.result != 'success' }} run: exit 1 - - run: echo "All checks and builds passed." + - run: echo "All checks, builds, and smoke tests passed." release-github: - if: github.ref == 'refs/heads/main' && github.actor != 'dependabot[bot]' && needs.release-info.outputs.changelog != '' && needs.release-info.outputs.version != '' + if: >- + needs.release-info.outputs.changelog != '' && needs.release-info.outputs.version != '' && + ( + (github.ref == 'refs/heads/main' && github.actor != 'dependabot[bot]') || + needs.release-info.outputs.pre_release == 'true' + ) needs: [release-info, branch-protection-checkpoint] runs-on: ubuntu-latest permissions: contents: write + env: + RELEASE_TARGET_COMMIT: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6 with: - fetch-depth: 0 - - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # ratchet:jdx/mise-action@v3 - - run: git tag ${{ needs.release-info.outputs.version }} - - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # ratchet:actions/download-artifact@v6 + ref: ${{ env.RELEASE_TARGET_COMMIT }} + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # ratchet:jdx/mise-action@v4 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # ratchet:actions/download-artifact@v8 with: merge-multiple: true path: release_artifacts @@ -165,15 +219,18 @@ jobs: id: release with: tag_name: ${{ needs.release-info.outputs.version }} + target_commitish: ${{ env.RELEASE_TARGET_COMMIT }} body: ${{ needs.release-info.outputs.changelog }} - prerelease: false + prerelease: ${{ needs.release-info.outputs.pre_release == 'true' }} files: ./release_artifacts/* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - env: + - if: needs.release-info.outputs.pre_release != 'true' + env: VERSION: ${{ needs.release-info.outputs.version }} run: mise run ci:prepare-template-vars ./release_artifacts/checksums.txt -v > template.vars - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # ratchet:actions/upload-artifact@v5 + - if: needs.release-info.outputs.pre_release != 'true' + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # ratchet:actions/upload-artifact@v7 with: name: template-vars path: ./template.vars @@ -182,12 +239,13 @@ jobs: echo "A new release is available over at https://github.com/${{ github.repository }}/releases/tag/${{ needs.release-info.outputs.version }}." >> $GITHUB_STEP_SUMMARY release-gar: + if: needs.release-info.outputs.pre_release != 'true' strategy: fail-fast: false matrix: arch: [arm64, amd64] suffix: [nav, tenant] - needs: [release-github] + needs: [release-info, release-github] runs-on: ubuntu-latest permissions: contents: read @@ -201,7 +259,7 @@ jobs: service_account: gh-naisdevice@nais-io.iam.gserviceaccount.com token_format: access_token - uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # ratchet:google-github-actions/setup-gcloud@v3 - - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # ratchet:actions/download-artifact@v6 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # ratchet:actions/download-artifact@v8 with: name: installer-linux-${{ matrix.arch }}-${{ matrix.suffix }} path: ./downloaded-artifact/ @@ -209,7 +267,8 @@ jobs: gcloud artifacts apt upload nais-ppa --project nais-io --quiet --location europe-north1 --source ./downloaded-artifact/* release-external-repos: - needs: [release-github] + if: needs.release-info.outputs.pre_release != 'true' + needs: [release-info, release-github] strategy: fail-fast: false matrix: @@ -234,8 +293,8 @@ jobs: private-key: ${{ secrets.NAIS_APP_PRIVATE_KEY }} app-id: ${{ secrets.NAIS_APP_ID }} repo: ${{ matrix.target.repo }} - - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # ratchet:jdx/mise-action@v3 - - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # ratchet:actions/download-artifact@v6 + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # ratchet:jdx/mise-action@v4 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # ratchet:actions/download-artifact@v8 with: name: template-vars - name: update ${{ matrix.target.repo }} diff --git a/.github/workflows/templates/naisdevice-tenant.rb b/.github/workflows/templates/naisdevice-tenant.rb index 4aa029407..ae07e7008 100644 --- a/.github/workflows/templates/naisdevice-tenant.rb +++ b/.github/workflows/templates/naisdevice-tenant.rb @@ -5,11 +5,6 @@ desc "naisdevice is a mechanism enabling developers to connect to internal resources in a secure and friendly manner." homepage "https://docs.nais.io/operate/naisdevice/how-to/install/" - depends_on formula: [ - "wireguard-go", - "wireguard-tools", - ] - if Hardware::CPU.intel? url "https://github.com/nais/device/releases/download/#{version}/$NAISDEVICE_TENANT_MACOS_AMD64_FILENAME", verified: "github.com/nais/device/" sha256 "$NAISDEVICE_TENANT_MACOS_AMD64_HASH_BASE16" diff --git a/.github/workflows/templates/naisdevice.rb b/.github/workflows/templates/naisdevice.rb index 2fcf0d988..1c8a8c78a 100644 --- a/.github/workflows/templates/naisdevice.rb +++ b/.github/workflows/templates/naisdevice.rb @@ -5,11 +5,6 @@ desc "naisdevice is a mechanism enabling developers to connect to internal resources in a secure and friendly manner." homepage "https://docs.nais.io/operate/naisdevice/how-to/install/" - depends_on formula: [ - "wireguard-go", - "wireguard-tools", - ] - if Hardware::CPU.intel? url "https://github.com/nais/device/releases/download/#{version}/$NAISDEVICE_MACOS_AMD64_FILENAME", verified: "github.com/nais/device/" sha256 "$NAISDEVICE_MACOS_AMD64_HASH_BASE16" diff --git a/.gitignore b/.gitignore index ed8a85e52..c3f0f0a3e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,6 @@ bin .DS_Store *.pkg *.app -wireguard-go-* -wireguard-tools-* cmd/device-agent/main_windows.syso cmd/helper/main_windows.syso packaging/windows/obj diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..a9662620b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ +# Project instructions for AI coding agents + +## Commands + +### After all changes are made, run + +- `mise run test` +- `mise run check` +- `go fix [changed_files]...` + +## Tech stack + +- go (look at go.mod for version) +- gRPC +- protobuf +- wireguard +- sqlite + +## Code style + +- Write obvious code instead of clever code. +- Favor self-explanatory code over code comments. +- If you really have to add a comment, make sure it's short and concise. +- Wrap errors with context: `fmt.Errorf("short description: %w", err)`. +- Use `testify/require` and `testify/assert` for tests. Prefer table-driven tests. +- Platform-specific code goes in files with build-tag suffixes (`_darwin.go`, `_linux.go`, `_windows.go`). + +## Git + +- Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for all commit messages. +- Never commit unless explicitly told to. +- Never push unless explicitly told to. diff --git a/assets/linux/nfpm.yaml b/assets/linux/nfpm.yaml index 43acee713..c4069219d 100644 --- a/assets/linux/nfpm.yaml +++ b/assets/linux/nfpm.yaml @@ -13,7 +13,7 @@ license: "MIT" depends: - "jq" - "sed" - - "wireguard" + - "wireguard | wireguard-tools" scripts: postinstall: "./assets/linux/postinstall" postremove: "./assets/linux/postrm" diff --git a/assets/linux/postinstall b/assets/linux/postinstall index f178dc30c..2c3558e18 100755 --- a/assets/linux/postinstall +++ b/assets/linux/postinstall @@ -28,7 +28,14 @@ make_user_dirs() { user_accounts=$(loginctl list-users --output json | jq '[.[] | select(.uid >= 1000)]') fi - if echo "$user_accounts" | jq -e 'length != 1' >/dev/null; then + num_accounts=$(echo "$user_accounts" | jq 'length') + + if [ "$num_accounts" -eq 0 ]; then + echo "No logged-in user accounts found, skipping user directory setup" + return 0 + fi + + if [ "$num_accounts" -gt 1 ]; then printf "\nMore than 1 user account logged in! naisdevice only permits _one_ user account!\n" exit 1 fi @@ -44,7 +51,9 @@ make_user_dirs() { chmod 700 "$directory" done - cp /sys/devices/virtual/dmi/id/product_serial "${config_dir}" + if [ -f /sys/devices/virtual/dmi/id/product_serial ]; then + cp /sys/devices/virtual/dmi/id/product_serial "${config_dir}" + fi chown -R "${user}:" "${config_dir}" } diff --git a/assets/macos/postinstall b/assets/macos/postinstall index 916192fce..0d2e1de85 100755 --- a/assets/macos/postinstall +++ b/assets/macos/postinstall @@ -5,14 +5,17 @@ daemon_name="io.nais.device.helper" destination="/Library/LaunchDaemons/${daemon_name}.plist" launchctl list | grep -q "$daemon_name" && launchctl unload "$destination" -config_dir="/Users/${user}/Library/Application Support/naisdevice" -log_dir="${config_dir}/logs" +if [ -n "$user" ]; then + config_dir="/Users/${user}/Library/Application Support/naisdevice" + log_dir="${config_dir}/logs" -mkdir -p -m 0700 "${config_dir}" -mkdir -p -m 0700 "${log_dir}" - -chown -R "${user}:staff" "${config_dir}" + mkdir -p -m 0700 "${config_dir}" + mkdir -p -m 0700 "${log_dir}" + chown -R "${user}:staff" "${config_dir}" +else + echo "No console user detected, skipping user config directory setup" +fi cat << EOF > "$destination" @@ -49,4 +52,3 @@ launchctl load "$destination" echo "Installed service $daemon_name" killall -9 -m "naisdevice.*" || true -killall -9 "wireguard-go" || true diff --git a/assets/windows/naisdevice.nsi b/assets/windows/naisdevice.nsi index 0016ccf29..944cd77cf 100644 --- a/assets/windows/naisdevice.nsi +++ b/assets/windows/naisdevice.nsi @@ -7,14 +7,6 @@ !define /ifndef VERSION "develop" ; Override when building release, MUST match '\d+.\d+.\d+.\d+' -!ifndef WIREGUARD - !error "Specify path to the WIREGUARD MSI file using -DWIREGUARD." -!endif - -!ifndef WIREGUARD_FILENAME - !error "Specify filename part of WIREGUARD variable using -DWIREGUARD_FILENAME." -!endif - !define APP_NAME "naisdevice" !define UNINSTALLER "uninstaller.exe" !define BIN_DIR "./bin/windows-client" @@ -86,7 +78,7 @@ Var ProgramDataPath !insertmacro MUI_PAGE_WELCOME Page custom StopInstances !insertmacro MUI_PAGE_INSTFILES -Page custom InstallWireGuard +Page custom StartService ;; Uninstaller pages @@ -121,6 +113,7 @@ Section "Install files" CreateDirectory $INSTDIR SetOutPath $INSTDIR File ${BIN_DIR}/naisdevice-*.exe + File ${BIN_DIR}/wintun.dll File ./assets/windows/icon/naisdevice.ico SectionEnd @@ -212,6 +205,7 @@ Section "Uninstall" GetKnownFolderPath $ProgramDataPath "${FOLDERID_ProgramData}" RMDir /r "$ProgramDataPath\NAV\naisdevice" Delete $INSTDIR\naisdevice-*.exe + Delete $INSTDIR\wintun.dll Delete $INSTDIR\naisdevice.ico Delete $INSTDIR\${UNINSTALLER} RMDir $INSTDIR @@ -221,6 +215,23 @@ SectionEnd ; Functions -------------------------------- +Function .onInstSuccess + ${IfNot} ${Silent} + Return + ${EndIf} + + !insertmacro _Log "Silent install: starting ${SERVICE_NAME} service" + SimpleSC::StartService ${SERVICE_NAME} "" 60 + Pop $0 + ${If} $0 != 0 + SimpleSC::GetErrorMessage + Pop $0 + !insertmacro _Log "Silent install: failed to start service: $0" + ${Else} + !insertmacro _Log "Silent install: service started successfully" + ${EndIf} +FunctionEnd + !macro GUIInit un Function ${un}GUIInit !insertmacro _Log "Inside GUIInit" @@ -402,31 +413,18 @@ ${ProgressPage} \ !insertmacro StopInstances "install" "" !insertmacro StopInstances "uninstall" "un." -; -- InstallWireGuard -------------- +; -- StartService -------------- ; Function that should push 0 on the stack to skip the page, any other value to continue (required) -Function _InstallWireGuard_Abort +Function _StartService_Abort ; Never skip Push 1 FunctionEnd ; Function to initialize any needed state, PP_NoOp to skip -Function _InstallWireGuard_Init - Push $R9 - - SetOutPath $TEMP - File /oname=${WIREGUARD_FILENAME} "${WIREGUARD}" - ExecWait 'msiexec /package "$TEMP\${WIREGUARD_FILENAME}" DO_NOT_LAUNCH=true' $R9 - ${If} ${Errors} - !insertmacro _Log "Error while installing WireGuard" - !insertmacro _Log "Exit code from wireguard installer: $R9" - ${EndIf} - - Pop $R9 -FunctionEnd ; Function called on every step. Should push 0 to the stack to leave the page, any other value to continue (required) -Function _InstallWireGuard_Step +Function _StartService_Step Push $R9 !insertmacro _Log "Attempting to start ${SERVICE_NAME} service" @@ -447,20 +445,14 @@ Function _InstallWireGuard_Step Pop $R9 FunctionEnd -; Function called when successfully leaving the page, PP_NoOp to skip -Function _InstallWireGuard_Leaving - Delete $TEMP\${WIREGUARD} -FunctionEnd - ${ProgressPage} \ - "InstallWireGuard" \ + "StartService" \ "Installation almost complete" \ - "Installing WireGuard and starting services" \ + "Starting services" \ "Installation of naisdevice is almost finished.$\n$\n\ - The final steps are to install WireGuard, which is used by naisdevice to create the VPN tunnels, and start background services.$\n$\n\ - The WireGuard installer finishes by launching WireGuard. You can close that window without making any changes.$\n$\n\ + The final step is to start the background service.$\n$\n\ Have a nais day!" \ - _InstallWireGuard_Abort \ - _InstallWireGuard_Init \ - _InstallWireGuard_Step \ - _InstallWireGuard_Leaving + _StartService_Abort \ + PP_NoOp \ + _StartService_Step \ + PP_NoOp diff --git a/assets/windows/wintun-amd64.dll b/assets/windows/wintun-amd64.dll new file mode 100644 index 000000000..aee04e77b Binary files /dev/null and b/assets/windows/wintun-amd64.dll differ diff --git a/assets/windows/wintun-arm64.dll b/assets/windows/wintun-arm64.dll new file mode 100644 index 000000000..dc4e4aeeb Binary files /dev/null and b/assets/windows/wintun-arm64.dll differ diff --git a/assets/windows/wintun.sha256 b/assets/windows/wintun.sha256 new file mode 100644 index 000000000..e488fea7e --- /dev/null +++ b/assets/windows/wintun.sha256 @@ -0,0 +1,2 @@ +e5da8447dc2c320edc0fc52fa01885c103de8c118481f683643cacc3220dafce wintun-amd64.dll +f7ba89005544be9d85231a9e0d5f23b2d15b3311667e2dad0debd344918a3f80 wintun-arm64.dll diff --git a/assets/windows/wireguard-amd64-0.5.3.msi b/assets/windows/wireguard-amd64-0.5.3.msi deleted file mode 100644 index f97ea5451..000000000 Binary files a/assets/windows/wireguard-amd64-0.5.3.msi and /dev/null differ diff --git a/assets/windows/wireguard-arm64-0.5.3.msi b/assets/windows/wireguard-arm64-0.5.3.msi deleted file mode 100644 index f940ec444..000000000 Binary files a/assets/windows/wireguard-arm64-0.5.3.msi and /dev/null differ diff --git a/cmd/apiserver/main.go b/cmd/apiserver/main.go index 3ef56486d..8a778b329 100644 --- a/cmd/apiserver/main.go +++ b/cmd/apiserver/main.go @@ -1,5 +1,3 @@ -//go:build linux - package main import ( @@ -160,7 +158,7 @@ func run(log *logrus.Entry, cfg config.Config) error { } cfg.WireGuardPrivateKey = key - netConf, err := wg.NewConfigurer(log.WithField("component", "network-configurer"), cfg.WireGuardConfigPath, cfg.WireGuardIPv4Prefix, cfg.WireGuardIPv6Prefix, string(cfg.WireGuardPrivateKey.Private()), "wg0", 51820, nil, nil, nil) + netConf, err := wg.NewConfigurer(log.WithField("component", "network-configurer"), cfg.WireGuardConfigPath, cfg.WireGuardIPv4Prefix, cfg.WireGuardIPv6Prefix, cfg.WireGuardPrivateKey.String(), "wg0", 51820, nil, nil, nil) if err != nil { return fmt.Errorf("create WireGuard configurer: %w", err) } diff --git a/cmd/gateway-agent/main.go b/cmd/gateway-agent/main.go index 798f8f441..676993179 100644 --- a/cmd/gateway-agent/main.go +++ b/cmd/gateway-agent/main.go @@ -1,5 +1,3 @@ -//go:build linux - package main import ( @@ -26,7 +24,6 @@ import ( "github.com/nais/device/internal/logger" "github.com/coreos/go-iptables/iptables" - "github.com/google/gopacket/routing" "github.com/kelseyhightower/envconfig" "github.com/sirupsen/logrus" @@ -82,7 +79,7 @@ func run(log *logrus.Entry, cfg config.Config) error { ecfg, err := enroll.NewGatewayClient( ctx, - privateKey.Public(), + privateKey.PublicKey().String(), hashedPassword, wireguardListenPort, log.WithField("component", "bootstrap"), @@ -99,7 +96,7 @@ func run(log *logrus.Entry, cfg config.Config) error { } cfg.Name = ecfg.Name - cfg.PrivateKey = string(privateKey.Private()) + cfg.PrivateKey = privateKey.String() cfg.APIServerURL = enrollResp.APIServerGRPCAddress cfg.DeviceIPv4 = enrollResp.WireGuardIPv4 @@ -121,14 +118,16 @@ func run(log *logrus.Entry, cfg config.Config) error { return fmt.Errorf("cannot enable routing: %w", err) } iptablesV4, err := iptables.NewWithProtocol(iptables.ProtocolIPv4) + _ = iptablesV4 // workaround as statickcheck thinks this in unused (but only on macos :shrug:) if err != nil { return fmt.Errorf("setup iptables: %w", err) } iptablesV6, err := iptables.NewWithProtocol(iptables.ProtocolIPv6) + _ = iptablesV6 // workaround as statickcheck thinks this in unused (but only on macos :shrug:) if err != nil { return fmt.Errorf("setup iptables: %w", err) } - router, err := routing.New() + router, err := NewRouter() if err != nil { return fmt.Errorf("setup routing: %w", err) } diff --git a/cmd/gateway-agent/main_linux.go b/cmd/gateway-agent/main_linux.go new file mode 100644 index 000000000..82c83b250 --- /dev/null +++ b/cmd/gateway-agent/main_linux.go @@ -0,0 +1,7 @@ +package main + +import "github.com/google/gopacket/routing" + +func NewRouter() (routing.Router, error) { + return routing.New() +} diff --git a/cmd/gateway-agent/main_other.go b/cmd/gateway-agent/main_other.go new file mode 100644 index 000000000..69f4fee8a --- /dev/null +++ b/cmd/gateway-agent/main_other.go @@ -0,0 +1,13 @@ +//go:build !linux + +package main + +import ( + "fmt" + + "github.com/google/gopacket/routing" +) + +func NewRouter() (routing.Router, error) { + return nil, fmt.Errorf("routing not supported on this platform") +} diff --git a/cmd/naisdevice-agent/main.go b/cmd/naisdevice-agent/main.go index 5d38b3d41..e33acbac1 100644 --- a/cmd/naisdevice-agent/main.go +++ b/cmd/naisdevice-agent/main.go @@ -291,9 +291,7 @@ func checkNewVersionAvailable(ctx context.Context) (bool, error) { span.RecordError(err) return false, err } - - client := &http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)} - resp, err := client.Do(req) + resp, err := otelhttp.DefaultClient.Do(req) if err != nil { span.RecordError(err) return false, fmt.Errorf("retrieve current release version: %s", err) diff --git a/cmd/naisdevice-helper/main.go b/cmd/naisdevice-helper/main.go index fec7ef981..83be493ec 100644 --- a/cmd/naisdevice-helper/main.go +++ b/cmd/naisdevice-helper/main.go @@ -20,12 +20,10 @@ import ( "github.com/nais/device/pkg/pb" ) -var cfg = helper.Config{ - WireGuardConfigPath: filepath.Join(config.ConfigDir, "utun69.conf"), -} +var cfg = helper.Config{} func init() { - flag.StringVar(&cfg.LogLevel, "log-level", "info", "which log level to output") + flag.StringVar(&cfg.LogLevel, "log-level", "debug", "which log level to output") flag.StringVar(&cfg.Interface, "interface", "utun69", "interface name") flag.Parse() @@ -54,7 +52,7 @@ func main() { log.WithError(err).Fatal("starting windows service") } - osConfigurator := helper.NewTracedConfigurator(helper.New(cfg)) + osConfigurator := helper.NewTracedConfigurator(helper.New(cfg, log)) log.WithFields(version.LogFields).WithField("cfg", cfg).Info("starting naisdevice-helper") diff --git a/cmd/prometheus-agent/main.go b/cmd/prometheus-agent/main.go index fb18bee4b..0878cfdfc 100644 --- a/cmd/prometheus-agent/main.go +++ b/cmd/prometheus-agent/main.go @@ -1,5 +1,3 @@ -//go:build linux - package main import ( diff --git a/cmd/smoke-test/main.go b/cmd/smoke-test/main.go new file mode 100644 index 000000000..490758ba6 --- /dev/null +++ b/cmd/smoke-test/main.go @@ -0,0 +1,153 @@ +// smoke-test connects to the running naisdevice-helper via gRPC, +// sends a Configure request with synthetic WireGuard keys and a test +// gateway, then verifies that: +// - the WireGuard interface has the expected peer +// - the OS has routes for the gateway's advertised prefixes +// +// Finally it tears down the configuration and exits. +package main + +import ( + "context" + "fmt" + "log" + "net/netip" + "os" + "path/filepath" + "time" + + "golang.zx2c4.com/wireguard/wgctrl" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/nais/device/internal/helper/config" + "github.com/nais/device/pkg/pb" +) + +const ifaceName = "utun69" + +var wantRoutes = []netip.Prefix{ + netip.MustParsePrefix("10.255.240.0/21"), + netip.MustParsePrefix("10.123.0.0/24"), + netip.MustParsePrefix("10.124.0.0/16"), +} + +func main() { + log.SetOutput(os.Stderr) + + if err := run(); err != nil { + log.Fatalf("FAIL: %v", err) + } + log.Println("PASS: smoke test succeeded") +} + +func run() error { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + socketPath := filepath.Join(config.RuntimeDir, "helper.sock") + conn, err := grpc.NewClient( + "unix:"+socketPath, + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + return fmt.Errorf("create gRPC client: %w", err) + } + defer func() { _ = conn.Close() }() + + client := pb.NewDeviceHelperClient(conn) + + log.Println("pinging helper...") + if _, err := client.Ping(ctx, &pb.PingRequest{}); err != nil { + return fmt.Errorf("ping helper: %w", err) + } + log.Println("helper is alive") + + privateKey, err := wgtypes.GeneratePrivateKey() + if err != nil { + return fmt.Errorf("generate private key: %w", err) + } + + gwPrivateKey, err := wgtypes.GeneratePrivateKey() + if err != nil { + return fmt.Errorf("generate gateway private key: %w", err) + } + gwPublicKey := gwPrivateKey.PublicKey() + + cfg := &pb.Configuration{ + PrivateKey: privateKey.String(), + DeviceIPv4: "10.255.240.100", + Gateways: []*pb.Gateway{ + { + Name: "smoke-gw", + PublicKey: gwPublicKey.String(), + Endpoint: "127.0.0.1:51820", + Ipv4: "10.255.240.1", + RoutesIPv4: []string{"10.123.0.0/24", "10.124.0.0/16"}, + }, + }, + } + + log.Println("sending Configure request...") + if _, err := client.Configure(ctx, cfg); err != nil { + return fmt.Errorf("configure helper: %w", err) + } + log.Println("configure succeeded") + + defer func() { + teardownCtx, teardownCancel := context.WithTimeout(context.Background(), 10*time.Second) + defer teardownCancel() + + log.Println("sending Teardown request...") + if _, err := client.Teardown(teardownCtx, &pb.TeardownRequest{}); err != nil { + log.Printf("WARN: teardown failed: %v", err) + return + } + log.Println("teardown succeeded") + }() + + if err := verifyPeers(gwPublicKey); err != nil { + return fmt.Errorf("verify peers: %w", err) + } + + if err := verifyRoutes(wantRoutes); err != nil { + return fmt.Errorf("verify routes: %w", err) + } + + return nil +} + +func verifyPeers(expectedPubKey wgtypes.Key) error { + log.Println("verifying WireGuard peers...") + + wgClient, err := wgctrl.New() + if err != nil { + return fmt.Errorf("create wgctrl client: %w", err) + } + defer func() { _ = wgClient.Close() }() + + dev, err := wgClient.Device(ifaceName) + if err != nil { + return fmt.Errorf("get device %q: %w", ifaceName, err) + } + + if len(dev.Peers) == 0 { + return fmt.Errorf("interface %q has no peers", ifaceName) + } + + found := false + for _, peer := range dev.Peers { + log.Printf(" peer: %s (endpoint=%v, allowed_ips=%v)", peer.PublicKey, peer.Endpoint, peer.AllowedIPs) + if peer.PublicKey == expectedPubKey { + found = true + } + } + + if !found { + return fmt.Errorf("expected peer %s not found on interface %q", expectedPubKey, ifaceName) + } + + log.Printf("PASS: interface %q has %d peer(s), expected peer found", ifaceName, len(dev.Peers)) + return nil +} diff --git a/cmd/smoke-test/verify_routes_darwin.go b/cmd/smoke-test/verify_routes_darwin.go new file mode 100644 index 000000000..ee7556a08 --- /dev/null +++ b/cmd/smoke-test/verify_routes_darwin.go @@ -0,0 +1,106 @@ +package main + +import ( + "fmt" + "log" + "net" + "net/netip" + "syscall" + + "golang.org/x/net/route" +) + +func verifyRoutes(prefixes []netip.Prefix) error { + log.Println("verifying macOS routes...") + + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + return fmt.Errorf("lookup interface %q: %w", ifaceName, err) + } + + rib, err := route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP, 0) + if err != nil { + return fmt.Errorf("fetch routing table: %w", err) + } + + msgs, err := route.ParseRIB(route.RIBTypeRoute, rib) + if err != nil { + return fmt.Errorf("parse routing table: %w", err) + } + + routeSet := make(map[netip.Prefix]bool) + for _, msg := range msgs { + rm, ok := msg.(*route.RouteMessage) + if !ok || rm.Index != iface.Index { + continue + } + p, ok := routeMsgToPrefix(rm) + if !ok { + continue + } + routeSet[p] = true + } + + for _, want := range prefixes { + if !routeSet[want] { + log.Printf("routes on interface %q (index %d):", ifaceName, iface.Index) + for p := range routeSet { + log.Printf(" %s", p) + } + return fmt.Errorf("expected route %s not found on interface %q", want, ifaceName) + } + log.Printf("PASS: route %s found", want) + } + + log.Printf("PASS: all expected routes present on interface %q", ifaceName) + return nil +} + +func routeMsgToPrefix(rm *route.RouteMessage) (netip.Prefix, bool) { + if len(rm.Addrs) <= syscall.RTAX_NETMASK { + return netip.Prefix{}, false + } + + dst := rm.Addrs[syscall.RTAX_DST] + mask := rm.Addrs[syscall.RTAX_NETMASK] + + addr, ok := addrToNetipAddr(dst) + if !ok { + return netip.Prefix{}, false + } + + bits := addr.BitLen() + if mask != nil { + bits = maskBits(mask, addr.Is6()) + } + + return netip.PrefixFrom(addr, bits), true +} + +func addrToNetipAddr(a route.Addr) (netip.Addr, bool) { + switch v := a.(type) { + case *route.Inet4Addr: + return netip.AddrFrom4(v.IP), true + case *route.Inet6Addr: + return netip.AddrFrom16(v.IP), true + default: + return netip.Addr{}, false + } +} + +func maskBits(a route.Addr, is6 bool) int { + switch v := a.(type) { + case *route.Inet4Addr: + ones, _ := net.IPv4Mask(v.IP[0], v.IP[1], v.IP[2], v.IP[3]).Size() + return ones + case *route.Inet6Addr: + mask := net.IPMask(v.IP[:]) + ones, _ := mask.Size() + return ones + default: + if is6 { + return 128 + } + return 32 + } +} diff --git a/cmd/smoke-test/verify_routes_linux.go b/cmd/smoke-test/verify_routes_linux.go new file mode 100644 index 000000000..d5e495a35 --- /dev/null +++ b/cmd/smoke-test/verify_routes_linux.go @@ -0,0 +1,50 @@ +package main + +import ( + "fmt" + "log" + "net/netip" + + "github.com/vishvananda/netlink" +) + +func verifyRoutes(prefixes []netip.Prefix) error { + log.Println("verifying Linux routes...") + + link, err := netlink.LinkByName(ifaceName) + if err != nil { + return fmt.Errorf("lookup interface %q: %w", ifaceName, err) + } + + routes, err := netlink.RouteList(link, netlink.FAMILY_ALL) + if err != nil { + return fmt.Errorf("list routes on %q: %w", ifaceName, err) + } + + routeSet := make(map[netip.Prefix]bool) + for _, r := range routes { + if r.Dst == nil { + continue + } + addr, ok := netip.AddrFromSlice(r.Dst.IP) + if !ok { + continue + } + ones, _ := r.Dst.Mask.Size() + routeSet[netip.PrefixFrom(addr.Unmap(), ones)] = true + } + + for _, want := range prefixes { + if !routeSet[want] { + log.Printf("routes on interface %q:", ifaceName) + for _, r := range routes { + log.Printf(" %s", r.Dst) + } + return fmt.Errorf("expected route %s not found on interface %q", want, ifaceName) + } + log.Printf("PASS: route %s found", want) + } + + log.Printf("PASS: all expected routes present on interface %q", ifaceName) + return nil +} diff --git a/cmd/smoke-test/verify_routes_windows.go b/cmd/smoke-test/verify_routes_windows.go new file mode 100644 index 000000000..72444646c --- /dev/null +++ b/cmd/smoke-test/verify_routes_windows.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "log" + "net" + "net/netip" + + "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" +) + +func verifyRoutes(prefixes []netip.Prefix) error { + log.Println("verifying Windows routes...") + + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + return fmt.Errorf("lookup interface %q: %w", ifaceName, err) + } + + ifLUID, err := winipcfg.LUIDFromIndex(uint32(iface.Index)) + if err != nil { + return fmt.Errorf("get LUID for interface index %d: %w", iface.Index, err) + } + + for _, prefix := range prefixes { + nextHop := netip.IPv4Unspecified() + if prefix.Addr().Is6() { + nextHop = netip.IPv6Unspecified() + } + _, err := ifLUID.Route(prefix, nextHop) + if err != nil { + return fmt.Errorf("expected route %s not found on interface %q: %w", prefix, ifaceName, err) + } + log.Printf("PASS: route %s found", prefix) + } + + log.Printf("PASS: all expected routes present on interface %q", ifaceName) + return nil +} diff --git a/go.mod b/go.mod index b62159525..3c8dc8d4c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/nais/device -go 1.26.3 +go 1.26.2 tool ( github.com/akavel/rsrc @@ -35,30 +35,35 @@ require ( github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 github.com/urfave/cli/v2 v2.27.7 + github.com/vishvananda/netlink v1.3.1 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 - go.opentelemetry.io/otel v1.43.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 - go.opentelemetry.io/otel/metric v1.43.0 - go.opentelemetry.io/otel/sdk v1.43.0 - go.opentelemetry.io/otel/sdk/metric v1.43.0 - go.opentelemetry.io/otel/trace v1.43.0 - golang.org/x/crypto v0.51.0 - golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f - golang.org/x/mod v0.35.0 - golang.org/x/oauth2 v0.35.0 - golang.org/x/sync v0.20.0 - golang.org/x/sys v0.45.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 + go.opentelemetry.io/otel v1.40.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 + go.opentelemetry.io/otel/metric v1.40.0 + go.opentelemetry.io/otel/sdk v1.40.0 + go.opentelemetry.io/otel/sdk/metric v1.40.0 + go.opentelemetry.io/otel/trace v1.40.0 + golang.org/x/crypto v0.48.0 + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b + golang.org/x/mod v0.33.0 + golang.org/x/net v0.51.0 + golang.org/x/oauth2 v0.34.0 + golang.org/x/sync v0.19.0 + golang.org/x/sys v0.41.0 + golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 + golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 + golang.zx2c4.com/wireguard/windows v0.5.3 google.golang.org/api v0.247.0 - google.golang.org/grpc v1.80.0 - google.golang.org/protobuf v1.36.11 + google.golang.org/grpc v1.77.0 + google.golang.org/protobuf v1.36.10 ) require ( 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect - cel.dev/expr v0.25.1 // indirect + cel.dev/expr v0.24.0 // indirect cloud.google.com/go v0.121.6 // indirect cloud.google.com/go/auth v0.16.5 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect @@ -82,7 +87,7 @@ require ( github.com/Antonboom/testifylint v1.6.4 // indirect github.com/BurntSushi/toml v1.6.0 // indirect github.com/Djarvur/go-err113 v0.1.1 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect @@ -122,6 +127,7 @@ require ( github.com/catenacyber/perfsprint v0.10.1 // indirect github.com/cavaliergopher/cpio v1.0.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.4 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.11 // indirect @@ -131,13 +137,13 @@ require ( github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/ckaznocha/intrange v0.3.1 // indirect - github.com/cloudflare/circl v1.6.3 // indirect - github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f // indirect github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cubicdaiya/gonp v1.0.4 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect - github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/daixiang0/gci v0.13.7 // indirect github.com/dave/dst v0.27.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -149,8 +155,8 @@ require ( github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect - github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.35.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/esiqveland/notify v0.13.3 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.18.0 // indirect @@ -163,9 +169,9 @@ require ( github.com/ghostiam/protogetter v0.3.20 // indirect github.com/go-critic/go-critic v0.14.3 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.9.0 // indirect - github.com/go-git/go-git/v5 v5.19.1 // indirect - github.com/go-jose/go-jose/v4 v4.1.4 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.16.5 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect @@ -213,7 +219,7 @@ require ( github.com/gostaticanalysis/comment v1.5.0 // indirect github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect github.com/gostaticanalysis/nilerr v0.1.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -235,13 +241,13 @@ require ( github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jjti/go-spancheck v0.6.5 // indirect + github.com/josharian/native v1.1.0 // indirect github.com/julz/importas v0.2.0 // indirect github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kisielk/errcheck v1.9.0 // indirect github.com/kkHAIKE/contextcheck v1.1.6 // indirect github.com/klauspost/compress v1.18.2 // indirect - github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect github.com/knadh/koanf/parsers/yaml v0.1.0 // indirect @@ -277,6 +283,9 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-sqlite3 v1.14.18 // indirect + github.com/mdlayher/genetlink v1.3.2 // indirect + github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/socket v0.5.1 // indirect github.com/mgechev/revive v1.14.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -302,7 +311,7 @@ require ( github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 // indirect github.com/pingcap/log v1.1.0 // indirect github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0 // indirect - github.com/pjbgf/sha1cd v0.6.0 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -364,6 +373,7 @@ require ( github.com/uudashr/iface v1.4.1 // indirect github.com/vbatts/tar-split v0.12.1 // indirect github.com/vektra/mockery/v3 v3.6.1 // indirect + github.com/vishvananda/netns v0.0.5 // indirect github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 // indirect github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect @@ -385,25 +395,25 @@ require ( go.augendre.info/fatcontext v0.9.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect - go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp/typeparams v0.0.0-20251125195548-87e1e737ad39 // indirect - golang.org/x/net v0.55.0 // indirect - golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa // indirect - golang.org/x/term v0.43.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect + golang.org/x/term v0.40.0 // indirect + golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.44.0 // indirect + golang.org/x/tools v0.42.0 // indirect golang.org/x/vuln v1.1.4 // indirect + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 605a053c2..a96996d0d 100644 --- a/go.sum +++ b/go.sum @@ -2,33 +2,142 @@ 4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= -cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= -cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= +cloud.google.com/go/accessapproval v1.8.6/go.mod h1:FfmTs7Emex5UvfnnpMkhuNkRCP85URnBFt5ClLxhZaQ= +cloud.google.com/go/accesscontextmanager v1.9.6/go.mod h1:884XHwy1AQpCX5Cj2VqYse77gfLaq9f8emE2bYriilk= +cloud.google.com/go/aiplatform v1.89.0/go.mod h1:TzZtegPkinfXTtXVvZZpxx7noINFMVDrLkE7cEWhYEk= +cloud.google.com/go/analytics v0.28.1/go.mod h1:iPaIVr5iXPB3JzkKPW1JddswksACRFl3NSHgVHsuYC4= +cloud.google.com/go/apigateway v1.7.6/go.mod h1:SiBx36VPjShaOCk8Emf63M2t2c1yF+I7mYZaId7OHiA= +cloud.google.com/go/apigeeconnect v1.7.6/go.mod h1:zqDhHY99YSn2li6OeEjFpAlhXYnXKl6DFb/fGu0ye2w= +cloud.google.com/go/apigeeregistry v0.9.6/go.mod h1:AFEepJBKPtGDfgabG2HWaLH453VVWWFFs3P4W00jbPs= +cloud.google.com/go/appengine v1.9.6/go.mod h1:jPp9T7Opvzl97qytaRGPwoH7pFI3GAcLDaui1K8PNjY= +cloud.google.com/go/area120 v0.9.6/go.mod h1:qKSokqe0iTmwBDA3tbLWonMEnh0pMAH4YxiceiHUed4= +cloud.google.com/go/artifactregistry v1.17.1/go.mod h1:06gLv5QwQPWtaudI2fWO37gfwwRUHwxm3gA8Fe568Hc= +cloud.google.com/go/asset v1.21.1/go.mod h1:7AzY1GCC+s1O73yzLM1IpHFLHz3ws2OigmCpOQHwebk= +cloud.google.com/go/assuredworkloads v1.12.6/go.mod h1:QyZHd7nH08fmZ+G4ElihV1zoZ7H0FQCpgS0YWtwjCKo= cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI= cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/automl v1.14.7/go.mod h1:8a4XbIH5pdvrReOU72oB+H3pOw2JBxo9XTk39oljObE= +cloud.google.com/go/baremetalsolution v1.3.6/go.mod h1:7/CS0LzpLccRGO0HL3q2Rofxas2JwjREKut414sE9iM= +cloud.google.com/go/batch v1.12.2/go.mod h1:tbnuTN/Iw59/n1yjAYKV2aZUjvMM2VJqAgvUgft6UEU= +cloud.google.com/go/beyondcorp v1.1.6/go.mod h1:V1PigSWPGh5L/vRRmyutfnjAbkxLI2aWqJDdxKbwvsQ= +cloud.google.com/go/bigquery v1.69.0/go.mod h1:TdGLquA3h/mGg+McX+GsqG9afAzTAcldMjqhdjHTLew= +cloud.google.com/go/bigtable v1.37.0/go.mod h1:HXqddP6hduwzrtiTCqZPpj9ij4hGZb4Zy1WF/dT+yaU= +cloud.google.com/go/billing v1.20.4/go.mod h1:hBm7iUmGKGCnBm6Wp439YgEdt+OnefEq/Ib9SlJYxIU= +cloud.google.com/go/binaryauthorization v1.9.5/go.mod h1:CV5GkS2eiY461Bzv+OH3r5/AsuB6zny+MruRju3ccB8= +cloud.google.com/go/certificatemanager v1.9.5/go.mod h1:kn7gxT/80oVGhjL8rurMUYD36AOimgtzSBPadtAeffs= +cloud.google.com/go/channel v1.19.5/go.mod h1:vevu+LK8Oy1Yuf7lcpDbkQQQm5I7oiY5fFTn3uwfQLY= +cloud.google.com/go/cloudbuild v1.22.2/go.mod h1:rPyXfINSgMqMZvuTk1DbZcbKYtvbYF/i9IXQ7eeEMIM= +cloud.google.com/go/clouddms v1.8.7/go.mod h1:DhWLd3nzHP8GoHkA6hOhso0R9Iou+IGggNqlVaq/KZ4= +cloud.google.com/go/cloudtasks v1.13.6/go.mod h1:/IDaQqGKMixD+ayM43CfsvWF2k36GeomEuy9gL4gLmU= +cloud.google.com/go/compute v1.38.0/go.mod h1:oAFNIuXOmXbK/ssXm3z4nZB8ckPdjltJ7xhHCdbWFZM= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/contactcenterinsights v1.17.3/go.mod h1:7Uu2CpxS3f6XxhRdlEzYAkrChpR5P5QfcdGAFEdHOG8= +cloud.google.com/go/container v1.43.0/go.mod h1:ETU9WZ1KM9ikEKLzrhRVao7KHtalDQu6aPqM34zDr/U= +cloud.google.com/go/containeranalysis v0.14.1/go.mod h1:28e+tlZgauWGHmEbnI5UfIsjMmrkoR1tFN0K2i71jBI= +cloud.google.com/go/datacatalog v1.26.0/go.mod h1:bLN2HLBAwB3kLTFT5ZKLHVPj/weNz6bR0c7nYp0LE14= +cloud.google.com/go/dataflow v0.11.0/go.mod h1:gNHC9fUjlV9miu0hd4oQaXibIuVYTQvZhMdPievKsPk= +cloud.google.com/go/dataform v0.12.0/go.mod h1:PuDIEY0lSVuPrZqcFji1fmr5RRvz3DGz4YP/cONc8g4= +cloud.google.com/go/datafusion v1.8.6/go.mod h1:fCyKJF2zUKC+O3hc2F9ja5EUCAbT4zcH692z8HiFZFw= +cloud.google.com/go/datalabeling v0.9.6/go.mod h1:n7o4x0vtPensZOoFwFa4UfZgkSZm8Qs0Pg/T3kQjXSM= +cloud.google.com/go/dataplex v1.25.3/go.mod h1:wOJXnOg6bem0tyslu4hZBTncfqcPNDpYGKzed3+bd+E= +cloud.google.com/go/dataproc/v2 v2.11.2/go.mod h1:xwukBjtfiO4vMEa1VdqyFLqJmcv7t3lo+PbLDcTEw+g= +cloud.google.com/go/dataqna v0.9.7/go.mod h1:4ac3r7zm7Wqm8NAc8sDIDM0v7Dz7d1e/1Ka1yMFanUM= +cloud.google.com/go/datastore v1.20.0/go.mod h1:uFo3e+aEpRfHgtp5pp0+6M0o147KoPaYNaPAKpfh8Ew= +cloud.google.com/go/datastream v1.14.1/go.mod h1:JqMKXq/e0OMkEgfYe0nP+lDye5G2IhIlmencWxmesMo= +cloud.google.com/go/deploy v1.27.2/go.mod h1:4NHWE7ENry2A4O1i/4iAPfXHnJCZ01xckAKpZQwhg1M= +cloud.google.com/go/dialogflow v1.68.2/go.mod h1:E0Ocrhf5/nANZzBju8RX8rONf0PuIvz2fVj3XkbAhiY= +cloud.google.com/go/dlp v1.23.0/go.mod h1:vVT4RlyPMEMcVHexdPT6iMVac3seq3l6b8UPdYpgFrg= +cloud.google.com/go/documentai v1.37.0/go.mod h1:qAf3ewuIUJgvSHQmmUWvM3Ogsr5A16U2WPHmiJldvLA= +cloud.google.com/go/domains v0.10.6/go.mod h1:3xzG+hASKsVBA8dOPc4cIaoV3OdBHl1qgUpAvXK7pGY= +cloud.google.com/go/edgecontainer v1.4.3/go.mod h1:q9Ojw2ox0uhAvFisnfPRAXFTB1nfRIOIXVWzdXMZLcE= +cloud.google.com/go/errorreporting v0.3.2/go.mod h1:s5kjs5r3l6A8UUyIsgvAhGq6tkqyBCUss0FRpsoVTww= +cloud.google.com/go/essentialcontacts v1.7.6/go.mod h1:/Ycn2egr4+XfmAfxpLYsJeJlVf9MVnq9V7OMQr9R4lA= +cloud.google.com/go/eventarc v1.15.5/go.mod h1:vDCqGqyY7SRiickhEGt1Zhuj81Ya4F/NtwwL3OZNskg= +cloud.google.com/go/filestore v1.10.2/go.mod h1:w0Pr8uQeSRQfCPRsL0sYKW6NKyooRgixCkV9yyLykR4= +cloud.google.com/go/firestore v1.18.0/go.mod h1:5ye0v48PhseZBdcl0qbl3uttu7FIEwEYVaWm0UIEOEU= +cloud.google.com/go/functions v1.19.6/go.mod h1:0G0RnIlbM4MJEycfbPZlCzSf2lPOjL7toLDwl+r0ZBw= +cloud.google.com/go/gkebackup v1.8.0/go.mod h1:FjsjNldDilC9MWKEHExnK3kKJyTDaSdO1vF0QeWSOPU= +cloud.google.com/go/gkeconnect v0.12.4/go.mod h1:bvpU9EbBpZnXGo3nqJ1pzbHWIfA9fYqgBMJ1VjxaZdk= +cloud.google.com/go/gkehub v0.15.6/go.mod h1:sRT0cOPAgI1jUJrS3gzwdYCJ1NEzVVwmnMKEwrS2QaM= +cloud.google.com/go/gkemulticloud v1.5.3/go.mod h1:KPFf+/RcfvmuScqwS9/2MF5exZAmXSuoSLPuaQ98Xlk= +cloud.google.com/go/gsuiteaddons v1.7.7/go.mod h1:zTGmmKG/GEBCONsvMOY2ckDiEsq3FN+lzWGUiXccF9o= cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= +cloud.google.com/go/iap v1.11.2/go.mod h1:Bh99DMUpP5CitL9lK0BC8MYgjjYO4b3FbyhgW1VHJvg= +cloud.google.com/go/ids v1.5.6/go.mod h1:y3SGLmEf9KiwKsH7OHvYYVNIJAtXybqsD2z8gppsziQ= +cloud.google.com/go/iot v1.8.6/go.mod h1:MThnkiihNkMysWNeNje2Hp0GSOpEq2Wkb/DkBCVYa0U= cloud.google.com/go/kms v1.22.0 h1:dBRIj7+GDeeEvatJeTB19oYZNV0aj6wEqSIT/7gLqtk= cloud.google.com/go/kms v1.22.0/go.mod h1:U7mf8Sva5jpOb4bxYZdtw/9zsbIjrklYwPcvMk34AL8= +cloud.google.com/go/language v1.14.5/go.mod h1:nl2cyAVjcBct1Hk73tzxuKebk0t2eULFCaruhetdZIA= +cloud.google.com/go/lifesciences v0.10.6/go.mod h1:1nnZwaZcBThDujs9wXzECnd1S5d+UiDkPuJWAmhRi7Q= cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= +cloud.google.com/go/managedidentities v1.7.6/go.mod h1:pYCWPaI1AvR8Q027Vtp+SFSM/VOVgbjBF4rxp1/z5p4= +cloud.google.com/go/maps v1.21.0/go.mod h1:cqzZ7+DWUKKbPTgqE+KuNQtiCRyg/o7WZF9zDQk+HQs= +cloud.google.com/go/mediatranslation v0.9.6/go.mod h1:WS3QmObhRtr2Xu5laJBQSsjnWFPPthsyetlOyT9fJvE= +cloud.google.com/go/memcache v1.11.6/go.mod h1:ZM6xr1mw3F8TWO+In7eq9rKlJc3jlX2MDt4+4H+/+cc= +cloud.google.com/go/metastore v1.14.7/go.mod h1:0dka99KQofeUgdfu+K/Jk1KeT9veWZlxuZdJpZPtuYU= cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= +cloud.google.com/go/networkconnectivity v1.17.1/go.mod h1:DTZCq8POTkHgAlOAAEDQF3cMEr/B9k1ZbpklqvHEBtg= +cloud.google.com/go/networkmanagement v1.19.1/go.mod h1:icgk265dNnilxQzpr6rO9WuAuuCmUOqq9H6WBeM2Af4= +cloud.google.com/go/networksecurity v0.10.6/go.mod h1:FTZvabFPvK2kR/MRIH3l/OoQ/i53eSix2KA1vhBMJec= +cloud.google.com/go/notebooks v1.12.6/go.mod h1:3Z4TMEqAKP3pu6DI/U+aEXrNJw9hGZIVbp+l3zw8EuA= +cloud.google.com/go/optimization v1.7.6/go.mod h1:4MeQslrSJGv+FY4rg0hnZBR/tBX2awJ1gXYp6jZpsYY= +cloud.google.com/go/orchestration v1.11.9/go.mod h1:KKXK67ROQaPt7AxUS1V/iK0Gs8yabn3bzJ1cLHw4XBg= +cloud.google.com/go/orgpolicy v1.15.0/go.mod h1:NTQLwgS8N5cJtdfK55tAnMGtvPSsy95JJhESwYHaJVs= +cloud.google.com/go/osconfig v1.14.6/go.mod h1:LS39HDBH0IJDFgOUkhSZUHFQzmcWaCpYXLrc3A4CVzI= +cloud.google.com/go/oslogin v1.14.6/go.mod h1:xEvcRZTkMXHfNSKdZ8adxD6wvRzeyAq3cQX3F3kbMRw= +cloud.google.com/go/phishingprotection v0.9.6/go.mod h1:VmuGg03DCI0wRp/FLSvNyjFj+J8V7+uITgHjCD/x4RQ= +cloud.google.com/go/policytroubleshooter v1.11.6/go.mod h1:jdjYGIveoYolk38Dm2JjS5mPkn8IjVqPsDHccTMu3mY= +cloud.google.com/go/privatecatalog v0.10.7/go.mod h1:Fo/PF/B6m4A9vUYt0nEF1xd0U6Kk19/Je3eZGrQ6l60= cloud.google.com/go/pubsub v1.49.0 h1:5054IkbslnrMCgA2MAEPcsN3Ky+AyMpEZcii/DoySPo= cloud.google.com/go/pubsub v1.49.0/go.mod h1:K1FswTWP+C1tI/nfi3HQecoVeFvL4HUOB1tdaNXKhUY= +cloud.google.com/go/pubsublite v1.8.2/go.mod h1:4r8GSa9NznExjuLPEJlF1VjOPOpgf3IT6k8x/YgaOPI= +cloud.google.com/go/recaptchaenterprise/v2 v2.20.4/go.mod h1:3H8nb8j8N7Ss2eJ+zr+/H7gyorfzcxiDEtVBDvDjwDQ= +cloud.google.com/go/recommendationengine v0.9.6/go.mod h1:nZnjKJu1vvoxbmuRvLB5NwGuh6cDMMQdOLXTnkukUOE= +cloud.google.com/go/recommender v1.13.5/go.mod h1:v7x/fzk38oC62TsN5Qkdpn0eoMBh610UgArJtDIgH/E= +cloud.google.com/go/redis v1.18.2/go.mod h1:q6mPRhLiR2uLf584Lcl4tsiRn0xiFlu6fnJLwCORMtY= +cloud.google.com/go/resourcemanager v1.10.6/go.mod h1:VqMoDQ03W4yZmxzLPrB+RuAoVkHDS5tFUUQUhOtnRTg= +cloud.google.com/go/resourcesettings v1.8.3/go.mod h1:BzgfXFHIWOOmHe6ZV9+r3OWfpHJgnqXy8jqwx4zTMLw= +cloud.google.com/go/retail v1.21.0/go.mod h1:LuG+QvBdLfKfO+7nnF3eA3l1j4TQw3Sg+UqlUorquRc= +cloud.google.com/go/run v1.10.0/go.mod h1:z7/ZidaHOCjdn5dV0eojRbD+p8RczMk3A7Qi2L+koHg= +cloud.google.com/go/scheduler v1.11.7/go.mod h1:gqYs8ndLx2M5D0oMJh48aGS630YYvC432tHCnVWN13s= +cloud.google.com/go/secretmanager v1.14.7/go.mod h1:uRuB4F6NTFbg0vLQ6HsT7PSsfbY7FqHbtJP1J94qxGc= +cloud.google.com/go/security v1.18.5/go.mod h1:D1wuUkDwGqTKD0Nv7d4Fn2Dc53POJSmO4tlg1K1iS7s= +cloud.google.com/go/securitycenter v1.36.2/go.mod h1:80ocoXS4SNWxmpqeEPhttYrmlQzCPVGaPzL3wVcoJvE= +cloud.google.com/go/servicedirectory v1.12.6/go.mod h1:OojC1KhOMDYC45oyTn3Mup08FY/S0Kj7I58dxUMMTpg= +cloud.google.com/go/shell v1.8.6/go.mod h1:GNbTWf1QA/eEtYa+kWSr+ef/XTCDkUzRpV3JPw0LqSk= +cloud.google.com/go/spanner v1.82.0/go.mod h1:BzybQHFQ/NqGxvE/M+/iU29xgutJf7Q85/4U9RWMto0= +cloud.google.com/go/speech v1.27.1/go.mod h1:efCfklHFL4Flxcdt9gpEMEJh9MupaBzw3QiSOVeJ6ck= cloud.google.com/go/storage v1.57.2 h1:sVlym3cHGYhrp6XZKkKb+92I1V42ks2qKKpB0CF5Mb4= cloud.google.com/go/storage v1.57.2/go.mod h1:n5ijg4yiRXXpCu0sJTD6k+eMf7GRrJmPyr9YxLXGHOk= +cloud.google.com/go/storagetransfer v1.13.0/go.mod h1:+aov7guRxXBYgR3WCqedkyibbTICdQOiXOdpPcJCKl8= +cloud.google.com/go/talent v1.8.3/go.mod h1:oD3/BilJpJX8/ad8ZUAxlXHCslTg2YBbafFH3ciZSLQ= +cloud.google.com/go/texttospeech v1.13.0/go.mod h1:g/tW/m0VJnulGncDrAoad6WdELMTes8eb77Idz+4HCo= +cloud.google.com/go/tpu v1.8.3/go.mod h1:Do6Gq+/Jx6Xs3LcY2WhHyGwKDKVw++9jIJp+X+0rxRE= cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= +cloud.google.com/go/translate v1.12.5/go.mod h1:o/v+QG/bdtBV1d1edmtau0PwTfActvxPk/gtqdSDBi4= +cloud.google.com/go/video v1.24.0/go.mod h1:h6Bw4yUbGNEa9dH4qMtUMnj6cEf+OyOv/f2tb70G6Fk= +cloud.google.com/go/videointelligence v1.12.6/go.mod h1:/l34WMndN5/bt04lHodxiYchLVuWPQjCU6SaiTswrIw= +cloud.google.com/go/vision/v2 v2.9.5/go.mod h1:1SiNZPpypqZDbOzU052ZYRiyKjwOcyqgGgqQCI/nlx8= +cloud.google.com/go/vmmigration v1.8.6/go.mod h1:uZ6/KXmekwK3JmC8PzBM/cKQmq404TTfWtThF6bbf0U= +cloud.google.com/go/vmwareengine v1.3.5/go.mod h1:QuVu2/b/eo8zcIkxBYY5QSwiyEcAy6dInI7N+keI+Jg= +cloud.google.com/go/vpcaccess v1.8.6/go.mod h1:61yymNplV1hAbo8+kBOFO7Vs+4ZHYI244rSFgmsHC6E= +cloud.google.com/go/webrisk v1.11.1/go.mod h1:+9SaepGg2lcp1p0pXuHyz3R2Yi2fHKKb4c1Q9y0qbtA= +cloud.google.com/go/websecurityscanner v1.7.6/go.mod h1:ucaaTO5JESFn5f2pjdX01wGbQ8D6h79KHrmO2uGZeiY= +cloud.google.com/go/workflows v1.14.2/go.mod h1:5nqKjMD+MsJs41sJhdVrETgvD5cOK3hUcAs8ygqYvXQ= codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY= codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ= codeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh2an7YmodI= @@ -47,6 +156,8 @@ git.sr.ht/~jackmordaunt/go-toast v1.1.2 h1:/yrfI55LRt1M7H1vkaw+NaH1+L1CDxrqDltwm git.sr.ht/~jackmordaunt/go-toast v1.1.2/go.mod h1:jA4OqHKTQ4AFBdwrSnwnskUIIS3HYzlJSgdzCKqfavo= github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8= github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= github.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ= github.com/Abirdcfly/dupword v0.1.7/go.mod h1:K0DkBeOebJ4VyOICFdppB23Q0YMOgVafM0zYW0n9lF4= github.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo= @@ -61,9 +172,19 @@ github.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksuf github.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II= github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ= github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0/go.mod h1:2e8rMJtl2+2j+HXbTBwnyGpm5Nou7KhvSfxOq8JpTag= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest/adal v0.9.16/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g= @@ -103,8 +224,11 @@ github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= +github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ= github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q= github.com/alexkohler/prealloc v1.0.2 h1:MPo8cIkGkZytq7WNH9UHv3DIX1mPz1RatPXnZb0zHWQ= @@ -115,23 +239,44 @@ github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQ github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w= github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/anthropics/anthropic-sdk-go v1.19.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/ashanbrown/forbidigo/v2 v2.3.0 h1:OZZDOchCgsX5gvToVtEBoV2UWbFfI6RKQTir2UZzSxo= github.com/ashanbrown/forbidigo/v2 v2.3.0/go.mod h1:5p6VmsG5/1xx3E785W9fouMxIOkvY2rRV9nMdWadd6c= github.com/ashanbrown/makezero/v2 v2.1.0 h1:snuKYMbqosNokUKm+R6/+vOPs8yVAi46La7Ck6QYSaE= github.com/ashanbrown/makezero/v2 v2.1.0/go.mod h1:aEGT/9q3S8DHeE57C88z2a6xydvgx8J5hgXIGWgo0MY= +github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go-v2 v1.16.16/go.mod h1:SwiyXi/1zTUZ6KIAmLK5V5ll8SiURNUYOqTerZPaF9k= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8/go.mod h1:JTnlBSot91steJeti4ryyu/tLd4Sk84O5W22L7O2EQU= +github.com/aws/aws-sdk-go-v2/credentials v1.12.20/go.mod h1:UKY5HyIux08bbNA7Blv4PcXQ8cTkGh7ghHMFklaviR4= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.33/go.mod h1:84XgODVR8uRhmOnUkKGUZKqIMxmjmLOR8Uyp7G/TPwc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23/go.mod h1:2DFxAQ9pfIRy0imBCJv+vZ2X6RKxves6fbnEuSry6b4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17/go.mod h1:pRwaTYCJemADaqCbUAxltMoHKata7hmB5PjEXeu0kfg= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14/go.mod h1:AyGgqiKv9ECM6IZeNQtdT8NnMvUb3/2wokeq2Fgryto= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9/go.mod h1:a9j48l6yL5XINLHLcOKInjdvknN+vWqPBxqeIDw7ktw= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18/go.mod h1:NS55eQ4YixUJPTC+INxi2/jCqe1y2Uw3rnh9wEOVJxY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17/go.mod h1:4nYOrY41Lrbk2170/BGkcJKBhws9Pfn8MG3aGqjjeFI= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17/go.mod h1:YqMdV+gEKCQ59NrB7rzrJdALeBIsYiVi8Inj3+KcqHI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11/go.mod h1:fmgDANqTUCxciViKl9hb/zD5LFbvPINFRgWhDbR+vZo= +github.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= @@ -156,6 +301,7 @@ github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70= github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/caarlos0/go-version v0.2.2 h1:5r+nlrg4H2wOVwWjqRqRRIRbZ7ytRmjC9xoMIP0a5kQ= github.com/caarlos0/go-version v0.2.2/go.mod h1:X+rI5VAtJDpcjCjeEIXpxGa5+rTcgur1FK66wS0/944= github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8= @@ -169,10 +315,13 @@ github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxY github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk= github.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4= +github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= +github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= @@ -181,6 +330,7 @@ github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7 github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs= @@ -188,9 +338,17 @@ github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nW github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= +github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= +github.com/cockroachdb/cockroach-go/v2 v2.1.1/go.mod h1:7NtUnP6eK+l6k483WSYNrq3Kb23bWV10IRV1TyeSpwM= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc= @@ -199,14 +357,21 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/cristalhq/acmd v0.12.0/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ= github.com/cubicdaiya/gonp v1.0.4 h1:ky2uIAJh81WiLcGKBVD5R7KsM/36W6IqqTy6Bo6rGws= github.com/cubicdaiya/gonp v1.0.4/go.mod h1:iWGuP/7+JVTn02OWhRemVbMmG1DOUnmrGTYYACpOI0I= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= +github.com/cznic/sortutil v0.0.0-20181122101858-f5f958428db8/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ= +github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= github.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ= github.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ= +github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY= github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= @@ -220,16 +385,24 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etly github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= +github.com/dhui/dktest v0.3.16/go.mod h1:gYaA3LRmM8Z4vJl2MA0THIigJoZrwOansEOsp+kqxp0= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/cli v28.3.0+incompatible h1:s+ttruVLhB5ayeuf2BciwDVxYdKi+RoUlxmwNHV3Vfo= github.com/docker/cli v28.3.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -237,35 +410,41 @@ github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FM github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= -github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= -github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= -github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= +github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= +github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs= +github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= +github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= -github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/esiqveland/notify v0.13.3 h1:QCMw6o1n+6rl+oLUfg8P1IIDSFsDEb2WlXvVvIJbI/o= github.com/esiqveland/notify v0.13.3/go.mod h1:hesw/IRYTO0x99u1JPweAl4+5mwXJibQVUcP0Iu5ORE= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= +github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E= github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo= +github.com/form3tech-oss/jwt-go v3.2.5+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= +github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M= github.com/gen2brain/beeep v0.11.1 h1:EbSIhrQZFDj1K2fzlMpAYlFOzV8YuNe721A58XcCTYI= github.com/gen2brain/beeep v0.11.1/go.mod h1:jQVvuwnLuwOcdctHn/uyh8horSBNJ8uGb9Cn2W4tvoc= github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0= @@ -295,6 +474,7 @@ github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7 github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= @@ -320,10 +500,13 @@ github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPE github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -331,9 +514,14 @@ github.com/godoc-lint/godoc-lint v0.11.1 h1:z9as8Qjiy6miRIa3VRymTa+Gt2RLnGICVikc github.com/godoc-lint/godoc-lint v0.11.1/go.mod h1:BAqayheFSuZrEAqCRxgw9MyvsM+S/hZwJbU1s/ejRj8= github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= @@ -350,6 +538,7 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0= github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ= github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= @@ -372,8 +561,11 @@ github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2b github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s= github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM= github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -390,8 +582,10 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU= github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y= +github.com/google/go-github/v39 v39.2.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE= github.com/google/go-github/v73 v73.0.0 h1:aR+Utnh+Y4mMkS+2qLQwcQ/cF9mOTpdwnzlaw//rG24= github.com/google/go-github/v73 v73.0.0/go.mod h1:fa6w8+/V+edSU0muqdhCVY7Beh1M8F1IlQPZIANKIYw= +github.com/google/go-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gopacket v1.1.20-0.20220810144506-32ee38206866 h1:NaJi58bCZZh0jjPw78EqDZekPEfhlzYE01C5R+zh1tE= @@ -413,6 +607,7 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs= @@ -423,6 +618,9 @@ github.com/goreleaser/fileglob v1.4.0 h1:Y7zcUnzQjT1gbntacGAkIIfLv+OwojxTXBFxjSF github.com/goreleaser/fileglob v1.4.0/go.mod h1:1pbHx7hhmJIxNZvm6fi6WVrnP0tndq6p3ayWdLn1Yf8= github.com/goreleaser/nfpm/v2 v2.44.0 h1:TlfLFJX/soK/I9GFbXMtP4SRM74s8sAfdBYNDYjCL8U= github.com/goreleaser/nfpm/v2 v2.44.0/go.mod h1:sLNhEIplQWuRK5QLxUsMCpkttUiM8lI1cH7rkjmziZU= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= @@ -437,6 +635,10 @@ github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+ github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -451,18 +653,27 @@ github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bP github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= @@ -483,25 +694,38 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= +github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0= github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= @@ -525,6 +749,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= github.com/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98= github.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs= github.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w= @@ -564,8 +789,14 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= +github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= +github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE= github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= +github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww= @@ -576,6 +807,7 @@ github.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6 github.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ= github.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs= github.com/maratori/testpackage v1.1.2/go.mod h1:8F24GdVDFW5Ew43Et02jamrVMNXLUNaOynhDssITGfc= +github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= @@ -588,22 +820,47 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI= github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= +github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= +github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/mgechev/dots v1.0.0/go.mod h1:rykuMydC9t3wfkM+ccYH3U3ss03vZGg6h3hmOznXLH0= github.com/mgechev/revive v1.14.0 h1:CC2Ulb3kV7JFYt+izwORoS3VT/+Plb8BvslI/l1yZsc= github.com/mgechev/revive v1.14.0/go.mod h1:MvnujelCZBZCaoDv5B3foPo6WWgULSSFxvfxp7GsPfo= +github.com/microsoft/go-mssqldb v1.0.0/go.mod h1:+4wZTUnz/SV6nffv+RRRB/ss8jPng5Sho2SmM1l2ts4= github.com/microsoft/wmi v0.38.3 h1:RVbn+m2jlPRsB2fLADXqabJj/EhMXQbvKM7OYS8VOv0= github.com/microsoft/wmi v0.38.3/go.mod h1:XF+cfluA15xGnSCYkJIYuj2vWzdm2YrNuvqlC+baWY0= +github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= +github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mozilla/tls-observatory v0.0.0-20250923143331-eef96233227e/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s= +github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/mango v0.2.0 h1:iNNc0c5VLQ6fsMgAqGQofByNUBH2Q2nEbD6TaI+5yyQ= github.com/muesli/mango v0.2.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4= github.com/muesli/mango-cobra v1.3.0 h1:vQy5GvPg3ndOSpduxutqFoINhWk3vD5K2dXo5E8pqec= @@ -616,12 +873,16 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nais/kolide-event-handler v0.0.0-20240619222557-6845c37b3249 h1:ucg9y24B++jdQXMRz+GErTp/izTiIkfmcSCYYShuszA= github.com/nais/kolide-event-handler v0.0.0-20240619222557-6845c37b3249/go.mod h1:1Ta1n1Q+EtH7EIHuU3/svAkCQ8AWk9/qfs6nBj0lhxE= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= +github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c h1:4RYnE0ISVwRxm9Dfo7utw1dh0kdRDEmVYq2MFVLy5zI= @@ -632,11 +893,14 @@ github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= github.com/nunnatsa/ginkgolinter v0.22.0 h1:o9g7JN6efdBxAHhejvPkodEjWsOBze9zDnPePsvC/Qg= github.com/nunnatsa/ginkgolinter v0.22.0/go.mod h1:zIFAk36fhcHQIiYOGXLbrGTXz7cvpufhRYem6ToCVnY= +github.com/nwoodmsft/iso9660 v0.0.0-20191211094520-15de0aa41e99/go.mod h1:oIuE6nqRO03jsO5LH16blMV11a3d2WF4mh8jZ19/g2A= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/openai/openai-go/v3 v3.8.1/go.mod h1:UOpNxkqC9OdNXNUfpNByKOtB4jAL0EssQXq5p8gO0Xs= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -648,10 +912,13 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pganalyze/pg_query_go/v6 v6.1.0 h1:jG5ZLhcVgL1FAw4C/0VNQaVmX1SUJx71wBGdtTtBvls= github.com/pganalyze/pg_query_go/v6 v6.1.0/go.mod h1:nvTHIuoud6e1SfrUaFwHqT0i4b5Nr+1rPWVds3B5+50= +github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= +github.com/pierrec/lz4/v4 v4.1.16/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb h1:3pSi4EDG6hg0orE1ndHkXvX6Qdq2cZn8gAPir8ymKZk= github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg= @@ -663,14 +930,19 @@ github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0 h1:W3rpAI3 github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0/go.mod h1:+8feuexTKcXHZF/dkDfvCwEyBAmgb4paFc3/WeYV2eE= github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU= github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -684,6 +956,7 @@ github.com/quasilyte/go-ruleguard v0.4.5 h1:AGY0tiOT5hJX9BTdx/xBdoCubQUAE2grkqY2 github.com/quasilyte/go-ruleguard v0.4.5/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE= github.com/quasilyte/go-ruleguard/dsl v0.3.23 h1:lxjt5B6ZCiBeeNO8/oQsegE6fLeCzuMRoVWSkXC4uvY= github.com/quasilyte/go-ruleguard/dsl v0.3.23/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= @@ -699,11 +972,13 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/riza-io/grpc-go v0.2.0 h1:2HxQKFVE7VuYstcJ8zqpN84VnAoJ4dCL6YFhJewNcHQ= github.com/riza-io/grpc-go v0.2.0/go.mod h1:2bDvR9KkKC3KhtlSHfR3dAXjUMT86kg4UfWFyVGWqi8= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g= @@ -714,6 +989,7 @@ github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsF github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= @@ -732,6 +1008,7 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sethvargo/ratchet v0.11.4 h1:Q6bEm6SexLS3G+PKOTY+ArkL9KM5anX+cZqOmLrtFhE= github.com/sethvargo/ratchet v0.11.4/go.mod h1:YVTmBPenzqMADQAycbYC9mhEdRm+nZ4KRIjNPsmE9sA= +github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= @@ -747,6 +1024,7 @@ github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGB github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= +github.com/snowflakedb/gosnowflake v1.6.19/go.mod h1:FM1+PWUdwB9udFDsXdfD58NONC0m+MlOSmQRvimobSM= github.com/sonatard/noctx v0.4.0 h1:7MC/5Gg4SQ4lhLYR6mvOP6mQVSxCrdyiExo7atBs27o= github.com/sonatard/noctx v0.4.0/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= @@ -759,6 +1037,7 @@ github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= @@ -804,10 +1083,16 @@ github.com/tetafro/godot v1.5.4 h1:u1ww+gqpRLiIA16yF2PV1CV1n/X3zhyezbNXC3E14Sg= github.com/tetafro/godot v1.5.4/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU= github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk= github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= github.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is= github.com/tomarrell/wrapcheck/v2 v2.12.0/go.mod h1:AQhQuZd0p7b6rfW+vUwHm5OMCGgp63moQ9Qr/0BpIWo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= @@ -818,29 +1103,38 @@ github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLk github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= +github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU= github.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/quicktemplate v1.8.0/go.mod h1:qIqW8/igXt8fdrUln5kOSb+KWMaJ4Y8QUsfd1k6L2jM= github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/vektra/mockery/v3 v3.6.1 h1:YyqAXihdNML8y6SJnvPKYr+2HAHvBjdvqFu/fMYlX8g= github.com/vektra/mockery/v3 v3.6.1/go.mod h1:Oti3Df0WP8wwT31yuVri3QNsDeMUQU5Q4QEg8EabaBw= -github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= +github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= +github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 h1:mJdDDPblDfPe7z7go8Dvv1AJQDI3eQ/5xith3q2mFlo= github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07/go.mod h1:Ak17IJ037caFp4jpCw/iQQ7/W74Sqpb1YuKJU6HTKfM= github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4= github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= +github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -849,6 +1143,7 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM= github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= @@ -861,16 +1156,21 @@ github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5Jsjqto github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= +gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= go-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo= @@ -883,12 +1183,13 @@ go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDoj go.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw= go.einride.tech/aip v0.68.1 h1:16/AfSxcQISGN5z9C5lM+0mLYXihrHbQ1onvYTr93aQ= go.einride.tech/aip v0.68.1/go.mod h1:XaFtaj4HuA3Zwk9xoBtTWgNubZ0ZZXv9BZJCkuKuWbg= +go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= -go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= +go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= +go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= @@ -913,6 +1214,38 @@ go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09 go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= +go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= +go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= +go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= +go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= +go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= +go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= +go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= +go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= +go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= +go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= +go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= +go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -992,6 +1325,13 @@ golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1026,9 +1366,11 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -1093,10 +1435,23 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= +golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= +golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU= +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ= +golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= +golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc= google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genai v1.37.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= @@ -1106,6 +1461,11 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1: google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -1113,6 +1473,10 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y= +google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6/go.mod h1:6ytKWczdvnpnO+m+JiG9NjEDzR1FJfsnmJdG7B8QVZ8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1126,14 +1490,20 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1145,28 +1515,41 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ= +gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg= +modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8= +modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw= modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM= modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/golex v1.1.0/go.mod h1:2pVlfqApurXhR1m0N+WDYu6Twnc4QuvO4+U8HnwoiRA= +modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= +modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/parser v1.1.0/go.mod h1:CXl3OTJRZij8FeMpzI3Id/bjupHf0u9HSrCUP4Z9pbA= +modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= @@ -1175,6 +1558,8 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/y v1.1.0/go.mod h1:Iz3BmyIS4OwAbwGaUS7cqRrLsSsfp2sFWtpzX+P4CsE= +modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4= mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4= mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s= mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI= diff --git a/internal/apiserver/config/config.go b/internal/apiserver/config/config.go index ed7cbdac1..9c12c8e38 100644 --- a/internal/apiserver/config/config.go +++ b/internal/apiserver/config/config.go @@ -11,6 +11,7 @@ import ( "github.com/nais/device/internal/wireguard" "github.com/nais/device/pkg/pb" "github.com/sirupsen/logrus" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) type Config struct { @@ -44,7 +45,7 @@ type Config struct { WireGuardIP string // for passing in raw string WireGuardIPv6 string // for passing in raw string WireGuardConfigPath string - WireGuardPrivateKey wireguard.PrivateKey + WireGuardPrivateKey wgtypes.Key WireGuardPrivateKeyPath string WireGuardNetworkAddress string WireGuardIPv4Prefix *netip.Prefix `ignored:"true"` @@ -141,7 +142,7 @@ func (cfg *Config) APIServerPeer() *pb.Gateway { return &pb.Gateway{ Name: "apiserver", - PublicKey: string(cfg.WireGuardPrivateKey.Public()), + PublicKey: cfg.WireGuardPrivateKey.PublicKey().String(), Endpoint: cfg.Endpoint, Ipv4: cfg.WireGuardIPv4Prefix.Addr().String(), Ipv6: ipv6, diff --git a/internal/apiserver/enroller/autoenroll_local.go b/internal/apiserver/enroller/autoenroll_local.go index 0089b41d3..015666f47 100644 --- a/internal/apiserver/enroller/autoenroll_local.go +++ b/internal/apiserver/enroller/autoenroll_local.go @@ -89,11 +89,18 @@ func (l *localEnroller) getEnrollments(ctx context.Context) ([]enroll.DeviceRequ } func (l *localEnroller) enroll(ctx context.Context, enrollment enroll.DeviceRequest) error { + normalizedPublicKey, err := enroll.NormalizeWireGuardPublicKey(enrollment.WireGuardPublicKey) + if err != nil { + msg := "local enroller: invalid wireguard public key" + l.log.WithError(err).Error(msg) + return fmt.Errorf("%s: %w", msg, err) + } + device := &pb.Device{ Username: enrollment.Owner, Serial: enrollment.Serial, Platform: enrollment.Platform, - PublicKey: string(enrollment.WireGuardPublicKey), + PublicKey: normalizedPublicKey, } if err := l.db.AddDevice(ctx, device); err != nil { diff --git a/internal/apiserver/enroller/autoenroll_pubsub.go b/internal/apiserver/enroller/autoenroll_pubsub.go index 789d65c36..516e28763 100644 --- a/internal/apiserver/enroller/autoenroll_pubsub.go +++ b/internal/apiserver/enroller/autoenroll_pubsub.go @@ -106,9 +106,16 @@ func (a *autoEnroll) receiveGateway(ctx context.Context, msg *pubsub.Message) { } log := a.log.WithField("gateway", req.Name) - err := a.db.AddGateway(ctx, &pb.Gateway{ + normalizedPublicKey, err := enroll.NormalizeWireGuardPublicKey(req.WireGuardPublicKey) + if err != nil { + log.WithError(err).Error("invalid wireguard public key") + msg.Nack() + return + } + + err = a.db.AddGateway(ctx, &pb.Gateway{ Name: req.Name, - PublicKey: string(req.WireGuardPublicKey), + PublicKey: normalizedPublicKey, Endpoint: req.Endpoint, PasswordHash: req.HashedPassword, }) @@ -171,9 +178,16 @@ func (a *autoEnroll) receiveDevice(ctx context.Context, msg *pubsub.Message) { } log := a.log.WithFields(logrus.Fields{"serial": req.Serial, "platform": req.Platform}) - err := a.db.AddDevice(ctx, &pb.Device{ + normalizedPublicKey, err := enroll.NormalizeWireGuardPublicKey(req.WireGuardPublicKey) + if err != nil { + log.WithError(err).Error("invalid wireguard public key") + msg.Nack() + return + } + + err = a.db.AddDevice(ctx, &pb.Device{ Username: req.Owner, - PublicKey: string(req.WireGuardPublicKey), + PublicKey: normalizedPublicKey, Serial: req.Serial, Platform: req.Platform, }) diff --git a/internal/controlplane-cli/enrollgateway.go b/internal/controlplane-cli/enrollgateway.go index f9551e94c..e4b00506b 100644 --- a/internal/controlplane-cli/enrollgateway.go +++ b/internal/controlplane-cli/enrollgateway.go @@ -7,10 +7,10 @@ import ( "io" "os" - "github.com/nais/device/internal/deviceagent/wireguard" "github.com/nais/device/internal/passwordhash" "github.com/nais/device/pkg/pb" "github.com/urfave/cli/v2" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) @@ -133,15 +133,18 @@ func EnrollGateway(c *cli.Context) error { key := passwordhash.HashPassword([]byte(password), salt) passhash := passwordhash.FormatHash(key, salt) - privateKey := wireguard.WgGenKey() - publicKey := wireguard.PublicKey(privateKey) + privateKey, err := wgtypes.GeneratePrivateKey() + if err != nil { + return fmt.Errorf("generating private key: %w", err) + } + publicKey := privateKey.PublicKey().String() req := &pb.ModifyGatewayRequest{ Username: AdminUsername, Password: c.String(FlagAdminPassword), Gateway: &pb.Gateway{ Name: c.String(FlagName), - PublicKey: string(publicKey), + PublicKey: publicKey, Endpoint: c.String(FlagEndpoint), PasswordHash: string(passhash), }, @@ -174,7 +177,7 @@ func EnrollGateway(c *cli.Context) error { fmt.Printf("GATEWAY_AGENT_NAME=\"%s\"\n", response.GetGateway().GetName()) fmt.Printf("GATEWAY_AGENT_APISERVERPASSWORD=\"%s\"\n", password) - fmt.Printf("GATEWAY_AGENT_PRIVATEKEY=\"%s\"\n", base64.StdEncoding.EncodeToString(privateKey)) + fmt.Printf("GATEWAY_AGENT_PRIVATEKEY=\"%s\"\n", privateKey.String()) fmt.Printf("GATEWAY_AGENT_DEVICEIP=\"%s/21\"\n", response.GetGateway().GetIpv4()) return err diff --git a/internal/deviceagent/config/config.go b/internal/deviceagent/config/config.go index 588ab0fdb..f0c44e4ae 100644 --- a/internal/deviceagent/config/config.go +++ b/internal/deviceagent/config/config.go @@ -34,7 +34,6 @@ type Config struct { JitaOAuth2Config oauth2.Config Platform string PrivateKeyPath string - WireGuardConfigPath string // TODO(sechmann):remove this as well EnrollProjectID string EnrollTopicName string LocalAPIServer bool @@ -46,7 +45,6 @@ func (c *Config) SetDefaults() { c.Platform = Platform c.Interface = "utun69" c.PrivateKeyPath = filepath.Join(c.ConfigDir, "private.key") - c.WireGuardConfigPath = filepath.Join(c.ConfigDir, c.Interface+".conf") } func DefaultConfig() (*Config, error) { diff --git a/internal/deviceagent/runtimeconfig/runtimeconfig.go b/internal/deviceagent/runtimeconfig/runtimeconfig.go index 1efb1ddd7..b30d5c975 100644 --- a/internal/deviceagent/runtimeconfig/runtimeconfig.go +++ b/internal/deviceagent/runtimeconfig/runtimeconfig.go @@ -3,7 +3,6 @@ package runtimeconfig import ( "bytes" "context" - "encoding/base64" "encoding/json" "fmt" "io" @@ -27,6 +26,7 @@ import ( "github.com/nais/device/pkg/pb" "github.com/sirupsen/logrus" "golang.org/x/oauth2" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "google.golang.org/api/iterator" "google.golang.org/api/option" "google.golang.org/grpc" @@ -77,7 +77,7 @@ var _ RuntimeConfig = &runtimeConfig{} type runtimeConfig struct { enrollConfig *bootstrap.Config // TODO: convert to enroll.Config config *config.Config - privateKey []byte + privateKey wgtypes.Key tokens *auth.Tokens tenants []*pb.Tenant log *logrus.Entry @@ -154,7 +154,7 @@ func (rc *runtimeConfig) ConnectToAPIServer(ctx context.Context) (pb.APIServerCl func (rc *runtimeConfig) BuildHelperConfiguration(peers []*pb.Gateway) *pb.Configuration { return &pb.Configuration{ - PrivateKey: base64.StdEncoding.EncodeToString(rc.privateKey), + PrivateKey: rc.privateKey.String(), DeviceIPv4: rc.enrollConfig.DeviceIPv4, DeviceIPv6: rc.enrollConfig.DeviceIPv6, Gateways: peers, @@ -207,7 +207,7 @@ func New(log *logrus.Entry, cfg *config.Config) (RuntimeConfig, error) { return nil, fmt.Errorf("ensuring private key: %w", err) } - rc.log.WithField("public_key", wireguard.PublicKey(rc.privateKey)).Info("runtime config initialized") + rc.log.WithField("public_key", rc.privateKey.PublicKey().String()).Info("runtime config initialized") return rc, nil } @@ -231,7 +231,7 @@ func (r *runtimeConfig) enroll(ctx context.Context, serial, token string) error req := &enroll.DeviceRequest{ Platform: r.config.Platform, Serial: serial, - WireGuardPublicKey: wireguard.PublicKey(r.privateKey), + WireGuardPublicKey: r.privateKey.PublicKey().String(), } buf := &bytes.Buffer{} diff --git a/internal/deviceagent/wireguard/wireguard.go b/internal/deviceagent/wireguard/wireguard.go index e3c080f0e..eb6fd815b 100644 --- a/internal/deviceagent/wireguard/wireguard.go +++ b/internal/deviceagent/wireguard/wireguard.go @@ -1,112 +1,52 @@ package wireguard import ( - "crypto/rand" - "encoding/base64" "fmt" - "io" "os" - "sort" "strings" - "github.com/nais/device/internal/wireguard" - "golang.org/x/crypto/curve25519" - "github.com/nais/device/internal/deviceagent/filesystem" - "github.com/nais/device/pkg/pb" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -func KeyToBase64(key []byte) []byte { - dst := make([]byte, base64.StdEncoding.EncodedLen(len(key))) - base64.StdEncoding.Encode(dst, key) - return dst -} - -func Base64toKey(encoded []byte) ([]byte, error) { - decoded := make([]byte, 32) - _, err := base64.StdEncoding.Decode(decoded, encoded) - if err != nil { - return nil, fmt.Errorf("decoding base64 key: %w", err) - } - - return decoded, nil -} - -func WgGenKey() []byte { - var privateKey [32]byte - - n, err := rand.Read(privateKey[:]) - - if err != nil || n != len(privateKey) { - panic("Unable to generate random bytes") - } - - privateKey[0] &= 248 - privateKey[31] = (privateKey[31] & 127) | 64 - return privateKey[:] -} - -func WGPubKey(privateKeySlice []byte) []byte { - var privateKey [32]byte - var publicKey [32]byte - copy(privateKey[:], privateKeySlice[:]) - - curve25519.ScalarBaseMult(&publicKey, &privateKey) - - return publicKey[:] -} - -func EnsurePrivateKey(keyPath string) ([]byte, error) { +func EnsurePrivateKey(keyPath string) (wgtypes.Key, error) { if err := filesystem.FileMustExist(keyPath); os.IsNotExist(err) { - if err := os.WriteFile(keyPath, KeyToBase64(WgGenKey()), 0o600); err != nil { - return nil, fmt.Errorf("writing private key to disk: %w", err) + key, err := wgtypes.GeneratePrivateKey() + if err != nil { + return wgtypes.Key{}, fmt.Errorf("generating private key: %w", err) } + if err := os.WriteFile(keyPath, []byte(key.String()), 0o600); err != nil { + return wgtypes.Key{}, fmt.Errorf("writing private key to disk: %w", err) + } + return key, nil } else if err != nil { - return nil, fmt.Errorf("ensuring private key exists: %w", err) + return wgtypes.Key{}, fmt.Errorf("ensuring private key exists: %w", err) } privateKeyEncoded, err := os.ReadFile(keyPath) if err != nil { - return nil, fmt.Errorf("reading private key: %v", err) + return wgtypes.Key{}, fmt.Errorf("reading private key: %w", err) } - privateKey, err := Base64toKey(privateKeyEncoded) - if err != nil { - return nil, fmt.Errorf("decoding private key: %v", err) + if key, err := wgtypes.ParseKey(strings.TrimSpace(string(privateKeyEncoded))); err == nil { + return key, nil } - return privateKey, nil -} - -func PublicKey(privateKey []byte) []byte { - return KeyToBase64(WGPubKey(privateKey)) -} - -func sortGateways(gateways []*pb.Gateway) { - if gateways == nil { - return + if len(privateKeyEncoded) != len(wgtypes.Key{}) { + return wgtypes.Key{}, fmt.Errorf("parsing private key: invalid key length %d", len(privateKeyEncoded)) } - sort.Slice(gateways, func(i, j int) bool { - return strings.Compare(gateways[i].Name, gateways[j].Name) < 0 - }) -} -func Marshal(w io.Writer, x *pb.Configuration) error { - // Sort gateways here to let windows helper detect changes in, and prevent unnecessary restarts - gateways := x.GetGateways() - sortGateways(gateways) - - cf := &wireguard.Config{ - Peers: wireguard.CastPeerList(gateways), - PrivateKey: x.GetPrivateKey(), + legacyKey, err := wgtypes.NewKey(privateKeyEncoded) + if err != nil { + return wgtypes.Key{}, fmt.Errorf("parsing legacy private key: %w", err) } - // Address configuration only supported on Windows - if windows { - cf.MTU = mtu - cf.AddressV4 = x.GetDeviceIPv4() - cf.AddressV6 = x.GetDeviceIPv6() + if err := os.WriteFile(keyPath, []byte(legacyKey.String()), 0o600); err != nil { + return wgtypes.Key{}, fmt.Errorf("rewriting legacy private key to disk: %w", err) + } + if err := os.Chmod(keyPath, 0o600); err != nil { + return wgtypes.Key{}, fmt.Errorf("setting private key file mode: %w", err) } - return cf.MarshalINI(w) + return legacyKey, nil } diff --git a/internal/deviceagent/wireguard/wireguard_test.go b/internal/deviceagent/wireguard/wireguard_test.go index 7211c83db..46b13aa16 100644 --- a/internal/deviceagent/wireguard/wireguard_test.go +++ b/internal/deviceagent/wireguard/wireguard_test.go @@ -1,15 +1,85 @@ package wireguard_test import ( + "os" + "path/filepath" "testing" "github.com/nais/device/internal/deviceagent/wireguard" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -func TestWGGenKey(t *testing.T) { - privateKey := wireguard.WgGenKey() - assert.Len(t, privateKey, 32) - privateKeyB64 := wireguard.KeyToBase64(privateKey) - assert.Len(t, privateKeyB64, 44) +func TestGenKey(t *testing.T) { + key, err := wgtypes.GeneratePrivateKey() + assert.NoError(t, err) + assert.Len(t, key, 32) + assert.Len(t, key.String(), 44) +} + +func TestEnsurePrivateKey_CreatesNewKey(t *testing.T) { + dir := t.TempDir() + keyPath := filepath.Join(dir, "private.key") + + key, err := wireguard.EnsurePrivateKey(keyPath) + require.NoError(t, err) + assert.NotEqual(t, wgtypes.Key{}, key, "generated key should not be zero") + + // Verify the key was written to disk + data, err := os.ReadFile(keyPath) + require.NoError(t, err) + assert.Equal(t, key.String(), string(data)) +} + +func TestEnsurePrivateKey_ReadsExistingKey(t *testing.T) { + dir := t.TempDir() + keyPath := filepath.Join(dir, "private.key") + + // Write a known key to disk + original, err := wgtypes.GeneratePrivateKey() + require.NoError(t, err) + require.NoError(t, os.WriteFile(keyPath, []byte(original.String()), 0o600)) + + // EnsurePrivateKey should read it back + key, err := wireguard.EnsurePrivateKey(keyPath) + require.NoError(t, err) + assert.Equal(t, original, key) +} + +func TestEnsurePrivateKey_IdempotentAcrossCalls(t *testing.T) { + dir := t.TempDir() + keyPath := filepath.Join(dir, "private.key") + + key1, err := wireguard.EnsurePrivateKey(keyPath) + require.NoError(t, err) + + key2, err := wireguard.EnsurePrivateKey(keyPath) + require.NoError(t, err) + + assert.Equal(t, key1, key2, "same key should be returned across calls") +} + +func TestEnsurePrivateKey_MigratesLegacyRawKey(t *testing.T) { + dir := t.TempDir() + keyPath := filepath.Join(dir, "private.key") + + original, err := wgtypes.GeneratePrivateKey() + require.NoError(t, err) + + legacyBytes := make([]byte, len(original)) + copy(legacyBytes, original[:]) + require.NoError(t, os.WriteFile(keyPath, legacyBytes, 0o644)) + + key, err := wireguard.EnsurePrivateKey(keyPath) + require.NoError(t, err) + assert.Equal(t, original, key) + + migrated, err := os.ReadFile(keyPath) + require.NoError(t, err) + assert.Equal(t, original.String(), string(migrated)) + + info, err := os.Stat(keyPath) + require.NoError(t, err) + assert.Equal(t, os.FileMode(0o600), info.Mode().Perm()) } diff --git a/internal/deviceagent/wireguard/wireguard_unix.go b/internal/deviceagent/wireguard/wireguard_unix.go deleted file mode 100644 index 288dc2c30..000000000 --- a/internal/deviceagent/wireguard/wireguard_unix.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build linux || darwin - -package wireguard - -import ( - "fmt" - "io" - - "github.com/nais/device/pkg/pb" -) - -var wireGuardTemplateHeader = `[Interface] -PrivateKey = %s - -` - -// mtu is no-op on non-windows platforms -const ( - mtu = 0 - windows = false -) - -func MarshalHeader(w io.Writer, x *pb.Configuration) (int, error) { - return fmt.Fprintf(w, wireGuardTemplateHeader, x.GetPrivateKey()) -} diff --git a/internal/deviceagent/wireguard/wireguard_unix_test.go b/internal/deviceagent/wireguard/wireguard_unix_test.go deleted file mode 100644 index 3a61281b8..000000000 --- a/internal/deviceagent/wireguard/wireguard_unix_test.go +++ /dev/null @@ -1,57 +0,0 @@ -//go:build linux || darwin - -package wireguard_test - -import ( - "bytes" - "testing" - - "github.com/nais/device/internal/deviceagent/wireguard" - "github.com/nais/device/pkg/pb" - "github.com/stretchr/testify/assert" -) - -func TestMarshalConfiguration(t *testing.T) { - cfg := &pb.Configuration{ - PrivateKey: "abc", - DeviceIPv4: "127.0.0.1", - Gateways: []*pb.Gateway{ - { - Name: "gateway-1", - PublicKey: "PQKmraPOPye5CJq1x7njpl8rRu5RSrIKyHvZXtLvS0E=", - Endpoint: "13.37.13.37:51820", - Ipv4: "10.255.240.2", - Ipv6: "fd00::2", - RoutesIPv4: []string{"13.37.69.0/24", "13.37.59.69/32"}, - }, - { - Name: "gateway-2", - PublicKey: "foobar", - Endpoint: "14.37.13.37:51820", - Ipv4: "11.255.240.2", - RoutesIPv4: []string{"14.37.69.0/24", "14.37.59.69/32"}, - }, - }, - } - - buf := new(bytes.Buffer) - err := wireguard.Marshal(buf, cfg) - - assert.NoError(t, err) - - expected := `[Interface] -PrivateKey = abc - -[Peer] # gateway-1 -PublicKey = PQKmraPOPye5CJq1x7njpl8rRu5RSrIKyHvZXtLvS0E= -AllowedIPs = 13.37.69.0/24,13.37.59.69/32,10.255.240.2/32,fd00::2/128 -Endpoint = 13.37.13.37:51820 - -[Peer] # gateway-2 -PublicKey = foobar -AllowedIPs = 14.37.69.0/24,14.37.59.69/32,11.255.240.2/32 -Endpoint = 14.37.13.37:51820 - -` - assert.Equal(t, expected, buf.String()) -} diff --git a/internal/deviceagent/wireguard/wireguard_windows.go b/internal/deviceagent/wireguard/wireguard_windows.go deleted file mode 100644 index 1c7932586..000000000 --- a/internal/deviceagent/wireguard/wireguard_windows.go +++ /dev/null @@ -1,27 +0,0 @@ -package wireguard - -import ( - "fmt" - "io" - - "github.com/nais/device/pkg/pb" -) - -/* -On windows we use "WireGuard-windows" client, which is basically a GUI wrapper of wg-quick. This config file requires -MTU and Address as additional fields because this also sets up the WireGuard interface for us. -*/ -var wireGuardTemplateHeader = `[Interface] -PrivateKey = %s -MTU = %d -Address = %s -` - -const ( - windows = true - mtu = 1360 -) - -func MarshalHeader(w io.Writer, x *pb.Configuration) (int, error) { - return fmt.Fprintf(w, wireGuardTemplateHeader, x.GetPrivateKey(), mtu, x.GetDeviceIPv4()) -} diff --git a/internal/deviceagent/wireguard/wireguard_windows_test.go b/internal/deviceagent/wireguard/wireguard_windows_test.go deleted file mode 100644 index 0240cd5b1..000000000 --- a/internal/deviceagent/wireguard/wireguard_windows_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package wireguard_test - -import ( - "bytes" - "testing" - - "github.com/nais/device/internal/deviceagent/wireguard" - "github.com/nais/device/pkg/pb" - "github.com/stretchr/testify/assert" -) - -func TestMarshalConfiguration(t *testing.T) { - cfg := &pb.Configuration{ - PrivateKey: "abc", - DeviceIP: "127.0.0.1", - Gateways: []*pb.Gateway{ - { - Name: "gateway-1", - PublicKey: "PQKmraPOPye5CJq1x7njpl8rRu5RSrIKyHvZXtLvS0E=", - Endpoint: "13.37.13.37:51820", - Ip: "10.255.240.2", - Routes: []string{"13.37.69.0/24", "13.37.59.69/32"}, - }, - { - Name: "gateway-2", - PublicKey: "foobar", - Endpoint: "14.37.13.37:51820", - Ip: "11.255.240.2", - Routes: []string{"14.37.69.0/24", "14.37.59.69/32"}, - }, - }, - } - - buf := new(bytes.Buffer) - err := wireguard.Marshal(buf, cfg) - - assert.NoError(t, err) - - expected := `[Interface] -PrivateKey = YWJj -MTU = 1360 -Address = 127.0.0.1 - -[Peer] # gateway-1 -PublicKey = PQKmraPOPye5CJq1x7njpl8rRu5RSrIKyHvZXtLvS0E= -AllowedIPs = 13.37.69.0/24,13.37.59.69/32,10.255.240.2/32 -Endpoint = 13.37.13.37:51820 - -[Peer] # gateway-2 -PublicKey = foobar -AllowedIPs = 14.37.69.0/24,14.37.59.69/32,11.255.240.2/32 -Endpoint = 14.37.13.37:51820 - -` - assert.Equal(t, expected, buf.String()) -} diff --git a/internal/enroll/enroll.go b/internal/enroll/enroll.go index b71898e0b..736661312 100644 --- a/internal/enroll/enroll.go +++ b/internal/enroll/enroll.go @@ -6,11 +6,11 @@ type DeviceRequest struct { Platform string `json:"platform"` Owner string `json:"owner"` Serial string `json:"serial"` - WireGuardPublicKey []byte `json:"wireguard_public_key"` + WireGuardPublicKey string `json:"wireguard_public_key"` } type GatewayRequest struct { - WireGuardPublicKey []byte `json:"wireguard_public_key"` + WireGuardPublicKey string `json:"wireguard_public_key"` Name string `json:"name"` Endpoint string `json:"endpoint"` HashedPassword string `json:"hashed_password"` diff --git a/internal/enroll/gateway.go b/internal/enroll/gateway.go index 2d8e6a3d2..420a94b87 100644 --- a/internal/enroll/gateway.go +++ b/internal/enroll/gateway.go @@ -16,7 +16,7 @@ import ( ) type GatewayClient struct { - wireGuardPublicKey []byte + wireGuardPublicKey string port int hashedPassword string log logrus.FieldLogger @@ -28,7 +28,7 @@ type GatewayClient struct { ExternalIP string `json:"external_ip"` } -func NewGatewayClient(ctx context.Context, publicKey []byte, hashedPassword string, wireguardListenPort int, log logrus.FieldLogger) (*GatewayClient, error) { +func NewGatewayClient(ctx context.Context, publicKey string, hashedPassword string, wireguardListenPort int, log logrus.FieldLogger) (*GatewayClient, error) { b, err := GetGoogleMetadata(ctx, "instance/attributes/enroll-config", log) if err != nil { return nil, err diff --git a/internal/enroll/handler.go b/internal/enroll/handler.go index f2ebf4948..e7f04e386 100644 --- a/internal/enroll/handler.go +++ b/internal/enroll/handler.go @@ -32,6 +32,15 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusBadRequest) return } + + normalizedPublicKey, err := NormalizeWireGuardPublicKey(req.WireGuardPublicKey) + if err != nil { + h.log.WithError(err).Error("invalid wireguard public key") + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + req.WireGuardPublicKey = normalizedPublicKey + req.Owner = token.GetEmail(r.Context()) resp, err := h.worker.Send(r.Context(), &req) diff --git a/internal/enroll/handler_test.go b/internal/enroll/handler_test.go new file mode 100644 index 000000000..93b9486f8 --- /dev/null +++ b/internal/enroll/handler_test.go @@ -0,0 +1,107 @@ +package enroll + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/nais/device/internal/token" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +type capturingWorker struct { + request *DeviceRequest + err error +} + +func (w *capturingWorker) Run(ctx context.Context) error { + return nil +} + +func (w *capturingWorker) Send(ctx context.Context, req *DeviceRequest) (*Response, error) { + w.request = req + if w.err != nil { + return nil, w.err + } + + return &Response{APIServerGRPCAddress: "test"}, nil +} + +func TestHandlerPublicKeyValidationAndNormalization(t *testing.T) { + privateKey, err := wgtypes.GeneratePrivateKey() + require.NoError(t, err) + + canonical := privateKey.PublicKey().String() + legacyEncoded := base64.StdEncoding.EncodeToString([]byte(canonical)) + + tests := []struct { + name string + publicKey string + wantStatus int + wantWorkerCalled bool + wantStoredPublicKey string + wantErrBody string + }{ + { + name: "canonical key", + publicKey: canonical, + wantStatus: http.StatusOK, + wantWorkerCalled: true, + wantStoredPublicKey: canonical, + }, + { + name: "legacy base64 encoded key", + publicKey: legacyEncoded, + wantStatus: http.StatusOK, + wantWorkerCalled: true, + wantStoredPublicKey: canonical, + }, + { + name: "invalid key", + publicKey: "invalid", + wantStatus: http.StatusBadRequest, + wantWorkerCalled: false, + wantErrBody: "invalid wireguard public key: expected canonical key or base64-encoded canonical key\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + worker := &capturingWorker{} + handler := NewHandler(worker, logrus.NewEntry(logrus.New())) + + payload, err := json.Marshal(&DeviceRequest{ + Platform: "darwin", + Serial: "serial-1", + WireGuardPublicKey: tt.publicKey, + }) + require.NoError(t, err) + + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(payload)) + req = req.WithContext(token.WithEmail(req.Context(), "user@example.com")) + rr := httptest.NewRecorder() + + handler.ServeHTTP(rr, req) + + assert.Equal(t, tt.wantStatus, rr.Code) + if tt.wantErrBody != "" { + assert.Equal(t, tt.wantErrBody, rr.Body.String()) + } + + if tt.wantWorkerCalled { + require.NotNil(t, worker.request) + assert.Equal(t, tt.wantStoredPublicKey, worker.request.WireGuardPublicKey) + assert.Equal(t, "user@example.com", worker.request.Owner) + } else { + assert.Nil(t, worker.request) + } + }) + } +} diff --git a/internal/enroll/publickey.go b/internal/enroll/publickey.go new file mode 100644 index 000000000..36527c1a4 --- /dev/null +++ b/internal/enroll/publickey.go @@ -0,0 +1,34 @@ +package enroll + +import ( + "encoding/base64" + "fmt" + "strings" + + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +func NormalizeWireGuardPublicKey(raw string) (string, error) { + canonicalCandidate := strings.TrimSpace(raw) + if canonicalCandidate == "" { + return "", fmt.Errorf("invalid wireguard public key: empty value") + } + + canonicalKey, canonicalErr := wgtypes.ParseKey(canonicalCandidate) + if canonicalErr == nil { + return canonicalKey.String(), nil + } + + decoded, decodeErr := base64.StdEncoding.DecodeString(canonicalCandidate) + if decodeErr != nil { + return "", fmt.Errorf("invalid wireguard public key: expected canonical key or base64-encoded canonical key") + } + + legacyCandidate := strings.TrimSpace(string(decoded)) + legacyKey, legacyErr := wgtypes.ParseKey(legacyCandidate) + if legacyErr != nil { + return "", fmt.Errorf("invalid wireguard public key: expected canonical key or base64-encoded canonical key") + } + + return legacyKey.String(), nil +} diff --git a/internal/enroll/publickey_test.go b/internal/enroll/publickey_test.go new file mode 100644 index 000000000..a373254cc --- /dev/null +++ b/internal/enroll/publickey_test.go @@ -0,0 +1,60 @@ +package enroll + +import ( + "encoding/base64" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +func TestNormalizeWireGuardPublicKey(t *testing.T) { + privateKey, err := wgtypes.GeneratePrivateKey() + require.NoError(t, err) + + canonical := privateKey.PublicKey().String() + legacyEncoded := base64.StdEncoding.EncodeToString([]byte(canonical)) + + tests := []struct { + name string + input string + want string + wantErr string + }{ + { + name: "canonical key", + input: canonical, + want: canonical, + }, + { + name: "legacy base64 encoded canonical key", + input: legacyEncoded, + want: canonical, + }, + { + name: "malformed key", + input: "not-a-wireguard-key", + wantErr: "invalid wireguard public key: expected canonical key or base64-encoded canonical key", + }, + { + name: "empty key", + input: " ", + wantErr: "invalid wireguard public key: empty value", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NormalizeWireGuardPublicKey(tt.input) + if tt.wantErr != "" { + require.Error(t, err) + assert.Equal(t, tt.wantErr, err.Error()) + return + } + + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/internal/helper/config.go b/internal/helper/config.go index 6f4d0a296..352478245 100644 --- a/internal/helper/config.go +++ b/internal/helper/config.go @@ -1,7 +1,6 @@ package helper type Config struct { - Interface string - LogLevel string - WireGuardConfigPath string + Interface string + LogLevel string } diff --git a/internal/helper/dns/dns_linux.go b/internal/helper/dns/dns_linux.go index 6b0c8b3e0..8fe99d01a 100644 --- a/internal/helper/dns/dns_linux.go +++ b/internal/helper/dns/dns_linux.go @@ -9,6 +9,8 @@ import ( "os/exec" "path/filepath" "strings" + + log "github.com/sirupsen/logrus" ) const ( @@ -16,6 +18,8 @@ const ( ) func apply(zones []string) error { + log.WithField("config_file", configFilePath).Debug("applying DNS config for systemd-resolved") + err := os.Mkdir(filepath.Dir(configFilePath), 0o755) if err != nil && !errors.Is(err, fs.ErrExist) { return err @@ -30,6 +34,7 @@ func apply(zones []string) error { if err != nil { return err } + log.WithField("zones", zones).Debug("wrote DNS config file") return reload() } @@ -50,10 +55,12 @@ DNSOverTLS=opportunistic } func reload() error { + log.Debug("restarting systemd-resolved") out, err := exec.Command("systemctl", "restart", "systemd-resolved.service").CombinedOutput() if err != nil { return fmt.Errorf("reloading systemd-resolved: %w: %s", err, string(out)) } + log.Debug("systemd-resolved restarted successfully") return nil } diff --git a/internal/helper/helper.go b/internal/helper/helper.go index 9152627b7..4a6e15776 100644 --- a/internal/helper/helper.go +++ b/internal/helper/helper.go @@ -6,20 +6,15 @@ package helper import ( - "bytes" "context" "fmt" - "io" - "os" "time" "github.com/nais/device/internal/helper/serial" - "github.com/nais/device/internal/ioconvenience" "github.com/sirupsen/logrus" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "github.com/nais/device/internal/deviceagent/wireguard" "github.com/nais/device/pkg/pb" ) @@ -57,16 +52,7 @@ func (dhs *DeviceHelperServer) Teardown( dhs.log.WithField("interface", dhs.config.Interface).Info("removing network interface and all routes") err := dhs.osConfigurator.TeardownInterface(ctx) if err != nil { - return nil, fmt.Errorf("tearing down interface: %v", err) - } - - dhs.log.Info("flushing WireGuard configuration from disk") - err = os.Remove(dhs.config.WireGuardConfigPath) - if err != nil { - if !os.IsNotExist(err) { - return nil, fmt.Errorf("flush WireGuard configuration from disk: %v", err) - } - dhs.log.Info("WireGuard configuration file does not exist on disk") + return nil, fmt.Errorf("tearing down interface: %w", err) } return &pb.TeardownResponse{}, nil @@ -76,28 +62,38 @@ func (dhs *DeviceHelperServer) Configure( ctx context.Context, cfg *pb.Configuration, ) (*pb.ConfigureResponse, error) { - dhs.log.Info("new configuration received from device-agent") - - err := dhs.writeConfigFile(cfg) - if err != nil { - return nil, status.Errorf(codes.ResourceExhausted, "write WireGuard configuration: %s", err) + dhs.log.WithField("num_gateways", len(cfg.GetGateways())).Info("new configuration received from device-agent") + for _, gw := range cfg.GetGateways() { + dhs.log.WithFields(logrus.Fields{ + "gateway": gw.GetName(), + "endpoint": gw.GetEndpoint(), + "ipv4": gw.GetIpv4(), + "ipv6": gw.GetIpv6(), + "routes_ipv4": gw.GetRoutesIPv4(), + "routes_ipv6": gw.GetRoutesIPv6(), + }).Debug("gateway in configuration") } - dhs.log.Info("wrote WireGuard config to disk") - - err = dhs.osConfigurator.SetupInterface(ctx, cfg) + dhs.log.Debug("setting up interface") + err := dhs.osConfigurator.SetupInterface(ctx, cfg) if err != nil { return nil, status.Errorf(codes.FailedPrecondition, "setup interface and routes: %s", err) } + dhs.log.Debug("interface setup complete") + dhs.log.Debug("syncing wireguard configuration") var loopErr error for attempt := range 5 { loopErr = dhs.osConfigurator.SyncConf(ctx, cfg) if loopErr != nil { - backoff := time.Duration(attempt) * time.Second + backoff := time.Duration(attempt+1) * time.Second dhs.log.WithError(loopErr).Error("synchronize WireGuard configuration") dhs.log.WithField("attempt", attempt+1).WithField("backoff", backoff).Info("configuring failed, sleeping before retrying") - time.Sleep(backoff) + select { + case <-ctx.Done(): + return nil, status.Errorf(codes.Canceled, "context canceled during WireGuard sync retry: %s", loopErr) + case <-time.After(backoff): + } continue } break @@ -109,41 +105,22 @@ func (dhs *DeviceHelperServer) Configure( loopErr, ) } + dhs.log.Debug("wireguard configuration synced") + // TODO: SetupRoutes only adds routes; it does not remove routes for gateways + // that were present in a previous configuration but are now absent. Stale routes + // are effectively harmless (they black-hole to a non-existent WireGuard peer), + // but a proper implementation should diff and clean up removed routes. + dhs.log.Debug("setting up routes") _, err = dhs.osConfigurator.SetupRoutes(ctx, cfg.GetGateways()) if err != nil { return nil, status.Errorf(codes.FailedPrecondition, "setting up routes: %s", err) } + dhs.log.Debug("routes setup complete, configure finished successfully") return &pb.ConfigureResponse{}, nil } -func (dhs *DeviceHelperServer) writeConfigFile(cfg *pb.Configuration) error { - buf := new(bytes.Buffer) - - err := wireguard.Marshal(buf, cfg) - if err != nil { - return fmt.Errorf("render configuration: %s", err) - } - - fd, err := os.OpenFile(dhs.config.WireGuardConfigPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o600) - if err != nil { - return fmt.Errorf("open file: %s", err) - } - defer ioconvenience.CloseWithLog(fd, dhs.log) - - _, err = io.Copy(fd, buf) - if err != nil { - return fmt.Errorf("write to disk: %s", err) - } - - if err := fd.Sync(); err != nil { - return fmt.Errorf("sync file: %s", err) - } - - return nil -} - func (dhs *DeviceHelperServer) GetSerial( context.Context, *pb.GetSerialRequest, diff --git a/internal/helper/helper_darwin.go b/internal/helper/helper_darwin.go index 0e994d761..2981c3341 100644 --- a/internal/helper/helper_darwin.go +++ b/internal/helper/helper_darwin.go @@ -2,96 +2,100 @@ package helper import ( "context" + "errors" "fmt" + "net" + "net/netip" "os" "os/exec" - "strings" + "sync" + "syscall" + "time" + "github.com/sirupsen/logrus" + "golang.org/x/net/route" + "golang.org/x/sys/unix" + "golang.zx2c4.com/wireguard/conn" + "golang.zx2c4.com/wireguard/device" + "golang.zx2c4.com/wireguard/ipc" + "golang.zx2c4.com/wireguard/tun" + + "github.com/nais/device/internal/iputil" + "github.com/nais/device/internal/wgconfig" "github.com/nais/device/pkg/pb" ) type DarwinConfigurator struct { - helperConfig Config - wireGuardBinary string - wireGuardGoBinary string + helperConfig Config + + mu sync.Mutex + wgDevice *device.Device + tunDev tun.Device + uapi net.Listener + ifaceName string // actual interface name assigned by macOS (may differ from requested) + tunnelNet netip.Prefix } var _ OSConfigurator = &DarwinConfigurator{} -func New(helperConfig Config) *DarwinConfigurator { +func New(helperConfig Config, _ *logrus.Entry) *DarwinConfigurator { return &DarwinConfigurator{ helperConfig: helperConfig, } } -func pathWithFallBacks(binary string, possiblePaths ...string) (string, error) { - if p, err := exec.LookPath(binary); err == nil { - return p, nil - } +func (c *DarwinConfigurator) Prerequisites() error { + return nil +} - for _, p := range possiblePaths { - if s, err := os.Stat(p); err == nil && !s.IsDir() { - return p, nil - } +func (c *DarwinConfigurator) SyncConf(ctx context.Context, cfg *pb.Configuration) error { + c.mu.Lock() + ifaceName := c.ifaceName + c.mu.Unlock() + if ifaceName == "" { + ifaceName = c.helperConfig.Interface } - - return "", fmt.Errorf("%q not found in PATH or any of %+v", binary, possiblePaths) + return wgconfig.ApplyConfig(ctx, ifaceName, cfg) } -func (c *DarwinConfigurator) Prerequisites() error { - var err error - - c.wireGuardBinary, err = pathWithFallBacks("wg", "/usr/local/bin/wg", "/opt/homebrew/bin/wg", "/usr/bin/wg") - if err != nil { - return fmt.Errorf("look for wg: %w", err) +func (c *DarwinConfigurator) SetupRoutes(ctx context.Context, gateways []*pb.Gateway) (int, error) { + c.mu.Lock() + ifaceName := c.ifaceName + tunnelNet := c.tunnelNet + c.mu.Unlock() + if ifaceName == "" { + ifaceName = c.helperConfig.Interface } - c.wireGuardGoBinary, err = pathWithFallBacks("wireguard-go", "/usr/local/bin/wireguard-go", "/opt/homebrew/bin/wireguard-go", "/usr/bin/wireguard-go") + iface, err := net.InterfaceByName(ifaceName) if err != nil { - return fmt.Errorf("look for wireguard-go: %w", err) + return 0, fmt.Errorf("lookup interface %q: %w", ifaceName, err) } - return nil -} - -func (c *DarwinConfigurator) SyncConf(ctx context.Context, cfg *pb.Configuration) error { - cmd := exec.CommandContext(ctx, c.wireGuardBinary, "syncconf", c.helperConfig.Interface, c.helperConfig.WireGuardConfigPath) - if b, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("running syncconf: %w: %v", err, string(b)) + fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC) + if err != nil { + return 0, fmt.Errorf("open routing socket: %w", err) } + defer func() { _ = unix.Close(fd) }() - return nil -} - -func (c *DarwinConfigurator) SetupRoutes(ctx context.Context, gateways []*pb.Gateway) (int, error) { routesAdded := 0 for _, gw := range gateways { - applyRoute := func(cidr, family string) error { - cmd := exec.CommandContext(ctx, "route", "-q", "-n", "add", family, cidr, "-interface", c.helperConfig.Interface) - output, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("executing %v, err: %w, stderr: %s", cmd, err, string(output)) - } - - return nil - } - for _, cidr := range gw.GetRoutesIPv4() { - if strings.HasPrefix(cidr, TunnelNetworkPrefix) { - // Don't add routes for the tunnel network, as the whole /21 net is already routed to utun + if IsTunnelRoute(tunnelNet, cidr) { continue } - err := applyRoute(cidr, "-inet") - if err != nil { - return routesAdded, err + if err := addRouteViaInterface(fd, cidr, iface); err != nil { + return routesAdded, fmt.Errorf("add IPv4 route %s: %w", cidr, err) } routesAdded++ } for _, cidr := range gw.GetRoutesIPv6() { - err := applyRoute(cidr, "-inet6") - if err != nil { - return routesAdded, err + if IsTunnelRoute(tunnelNet, cidr) { + continue + } + if err := addRouteViaInterface(fd, cidr, iface); err != nil { + return routesAdded, fmt.Errorf("add IPv6 route %s: %w", cidr, err) } routesAdded++ } @@ -101,37 +105,208 @@ func (c *DarwinConfigurator) SetupRoutes(ctx context.Context, gateways []*pb.Gat } func (c *DarwinConfigurator) SetupInterface(ctx context.Context, cfg *pb.Configuration) error { - if c.interfaceExists(ctx) { + c.mu.Lock() + defer c.mu.Unlock() + + tunnelNet, err := TunnelNetworkFromIP(cfg.GetDeviceIPv4()) + if err != nil { + return fmt.Errorf("derive tunnel network: %w", err) + } + c.tunnelNet = tunnelNet + + if c.wgDevice != nil { return nil } + tunDev, err := tun.CreateTUN(c.helperConfig.Interface, wireguardMTU) + if err != nil { + return fmt.Errorf("create TUN device: %w", err) + } + + ifaceName, err := tunDev.Name() + if err != nil { + _ = tunDev.Close() + return fmt.Errorf("get TUN device name: %w", err) + } + + logger := &device.Logger{ + Verbosef: device.DiscardLogf, + Errorf: func(format string, args ...any) { fmt.Fprintf(os.Stderr, "wireguard: "+format+"\n", args...) }, + } + wgDev := device.NewDevice(tunDev, conn.NewDefaultBind(), logger) + + if err := wgDev.Up(); err != nil { + wgDev.Close() + return fmt.Errorf("bring up wireguard device: %w", err) + } + + // UAPI socket allows wgctrl to configure the device + fileUAPI, err := ipc.UAPIOpen(ifaceName) + if err != nil { + wgDev.Close() + return fmt.Errorf("open UAPI socket: %w", err) + } + + uapi, err := ipc.UAPIListen(ifaceName, fileUAPI) + if err != nil { + _ = fileUAPI.Close() + wgDev.Close() + return fmt.Errorf("listen on UAPI socket: %w", err) + } + + c.wgDevice = wgDev + c.tunDev = tunDev + c.uapi = uapi + c.ifaceName = ifaceName + + // Configure IP addresses and bring the interface up. + // We shell out to ifconfig because macOS has no stable public API for assigning + // addresses to utun interfaces — the required SIOCAIFADDR_IN6 / SIOCSIFADDR + // ioctls are undocumented and vary across OS versions. ifconfig is the standard + // tool used by the macOS networking stack itself. commands := [][]string{ - {c.wireGuardGoBinary, c.helperConfig.Interface}, - {"ifconfig", c.helperConfig.Interface, "inet", cfg.GetDeviceIPv4() + "/21", cfg.GetDeviceIPv4(), "alias"}, - {"ifconfig", c.helperConfig.Interface, "inet6", cfg.GetDeviceIPv6() + "/64", "alias"}, - {"ifconfig", c.helperConfig.Interface, "mtu", "1360"}, - {"ifconfig", c.helperConfig.Interface, "up"}, - {"route", "-q", "-n", "add", "-inet", cfg.GetDeviceIPv4() + "/21", "-interface", c.helperConfig.Interface}, + {"ifconfig", ifaceName, "inet", cfg.GetDeviceIPv4() + "/21", cfg.GetDeviceIPv4(), "alias"}, } + if cfg.GetDeviceIPv6() != "" { + commands = append(commands, []string{"ifconfig", ifaceName, "inet6", cfg.GetDeviceIPv6() + "/64", "alias"}) + } + commands = append(commands, []string{"ifconfig", ifaceName, "up"}) + + for _, s := range commands { + cmd := exec.CommandContext(ctx, s[0], s[1:]...) - return runCommands(ctx, commands) + if out, err := cmd.CombinedOutput(); err != nil { + c.closeLocked() + return fmt.Errorf("running %v: %w: %v", cmd, err, string(out)) + } + + // Small delay between ifconfig calls to avoid kernel ENOMEM / EBUSY + // errors when rapidly configuring addresses on a freshly-created utun. + time.Sleep(100 * time.Millisecond) + } + + // Add /21 route for the tunnel network + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + c.closeLocked() + return fmt.Errorf("lookup interface %q: %w", ifaceName, err) + } + + routeFd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC) + if err != nil { + c.closeLocked() + return fmt.Errorf("open routing socket: %w", err) + } + defer func() { _ = unix.Close(routeFd) }() + + if err := addRouteViaInterface(routeFd, cfg.GetDeviceIPv4()+"/21", iface); err != nil { + c.closeLocked() + return fmt.Errorf("add tunnel network route: %w", err) + } + + // Start accepting UAPI connections only after all initialization has succeeded. + // This ensures wgctrl can't race against incomplete interface setup. + go func() { + for { + uapiConn, err := uapi.Accept() + if err != nil { + return + } + go wgDev.IpcHandle(uapiConn) + } + }() + + return nil } func (c *DarwinConfigurator) TeardownInterface(ctx context.Context) error { - if !c.interfaceExists(ctx) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.wgDevice == nil { return nil } - cmd := exec.CommandContext(ctx, "pkill", "-f", fmt.Sprintf("%s %s", c.wireGuardGoBinary, c.helperConfig.Interface)) - out, err := cmd.CombinedOutput() + c.closeLocked() + return nil +} + +// closeLocked shuts down the UAPI listener, WireGuard device, and TUN. +// Must be called with c.mu held. +func (c *DarwinConfigurator) closeLocked() { + if c.uapi != nil { + _ = c.uapi.Close() + c.uapi = nil + } + if c.wgDevice != nil { + c.wgDevice.Close() + c.wgDevice = nil + } + // wgDevice.Close() also closes tunDev + c.tunDev = nil + c.ifaceName = "" +} + +// addRouteViaInterface adds a route for the given CIDR through the specified +// network interface using the provided BSD routing socket fd. +func addRouteViaInterface(fd int, cidr string, iface *net.Interface) error { + prefix, err := iputil.ParsePrefix(cidr) if err != nil { - return fmt.Errorf("teardown failed: %w, stderr: %s", err, string(out)) + return fmt.Errorf("add route: %w", err) } - return nil -} + addrs := make([]route.Addr, syscall.RTAX_MAX) + + if prefix.Addr().Is6() { + dst := prefix.Addr().As16() + addrs[syscall.RTAX_DST] = &route.Inet6Addr{IP: dst} -func (c *DarwinConfigurator) interfaceExists(ctx context.Context) bool { - cmd := exec.CommandContext(ctx, "pgrep", "-f", fmt.Sprintf("%s %s", c.wireGuardGoBinary, c.helperConfig.Interface)) - return cmd.Run() == nil + var mask [16]byte + ones := prefix.Bits() + for i := range ones { + mask[i/8] |= 1 << (7 - i%8) + } + addrs[syscall.RTAX_NETMASK] = &route.Inet6Addr{IP: mask} + } else { + dst := prefix.Addr().As4() + addrs[syscall.RTAX_DST] = &route.Inet4Addr{IP: dst} + + var mask [4]byte + ones := prefix.Bits() + for i := range ones { + mask[i/8] |= 1 << (7 - i%8) + } + addrs[syscall.RTAX_NETMASK] = &route.Inet4Addr{IP: mask} + } + + // LinkAddr as gateway routes directly through the interface + addrs[syscall.RTAX_GATEWAY] = &route.LinkAddr{ + Index: iface.Index, + Name: iface.Name, + } + + rtm := &route.RouteMessage{ + Version: syscall.RTM_VERSION, + Type: syscall.RTM_ADD, + Flags: syscall.RTF_UP | syscall.RTF_STATIC, + Index: iface.Index, + ID: uintptr(os.Getpid()), + Seq: int(time.Now().UnixNano() & 0x7fffffff), + Addrs: addrs, + } + + b, err := rtm.Marshal() + if err != nil { + return fmt.Errorf("marshal route message: %w", err) + } + + _, err = unix.Write(fd, b) + if err != nil { + if errors.Is(err, unix.EEXIST) { + return nil + } + return fmt.Errorf("write route message: %w", err) + } + + return nil } diff --git a/internal/helper/helper_linux.go b/internal/helper/helper_linux.go index e47363c51..3aba6d7b5 100644 --- a/internal/helper/helper_linux.go +++ b/internal/helper/helper_linux.go @@ -2,120 +2,237 @@ package helper import ( "context" + "errors" "fmt" - "os/exec" - "strings" + "net" + "net/netip" + "sync" + "syscall" + "github.com/sirupsen/logrus" + "github.com/vishvananda/netlink" + + "github.com/nais/device/internal/iputil" + "github.com/nais/device/internal/wgconfig" "github.com/nais/device/pkg/pb" ) -var wireguardBinary = "" - -func New(helperConfig Config) *LinuxConfigurator { +func New(helperConfig Config, log *logrus.Entry) *LinuxConfigurator { return &LinuxConfigurator{ helperConfig: helperConfig, + log: log, } } type LinuxConfigurator struct { helperConfig Config + mu sync.RWMutex + tunnelNet netip.Prefix + log *logrus.Entry } var _ OSConfigurator = &LinuxConfigurator{} func (c *LinuxConfigurator) Prerequisites() error { - var err error - wireguardBinary, err = exec.LookPath("wg") - if err != nil { - return fmt.Errorf("unable to find wg binary: %w", err) - } - if wireguardBinary == "" { - return fmt.Errorf("wg path is empty string") - } - return nil } func (c *LinuxConfigurator) SyncConf(ctx context.Context, cfg *pb.Configuration) error { - cmd := exec.CommandContext( - ctx, - wireguardBinary, - "syncconf", - c.helperConfig.Interface, - c.helperConfig.WireGuardConfigPath, - ) - if b, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("running syncconf: %w: %v", err, string(b)) + for _, gw := range cfg.GetGateways() { + c.log.WithFields(logrus.Fields{ + "peer": gw.GetName(), + "endpoint": gw.GetEndpoint(), + "public_key": fmt.Sprintf("%.8s...", gw.GetPublicKey()), + "allowed_ips": gw.GetAllowedIPs(), + }).Debug("configuring wireguard peer") } - - return nil + err := wgconfig.ApplyConfig(ctx, c.helperConfig.Interface, cfg) + if err != nil { + c.log.WithError(err).Debug("wireguard config sync failed") + } else { + c.log.Debug("wireguard config sync complete") + } + return err } func (c *LinuxConfigurator) SetupRoutes(ctx context.Context, gateways []*pb.Gateway) (int, error) { + c.mu.RLock() + tunnelNet := c.tunnelNet + c.mu.RUnlock() + + link, err := netlink.LinkByName(c.helperConfig.Interface) + if err != nil { + return 0, fmt.Errorf("lookup interface %q: %w", c.helperConfig.Interface, err) + } + + c.log.WithFields(logrus.Fields{ + "interface": c.helperConfig.Interface, + "link_index": link.Attrs().Index, + "num_gateways": len(gateways), + }).Debug("setting up routes") + routesAdded := 0 for _, gw := range gateways { - // For Linux we can handle ipv4/6 addreses the same - the `ip` utility handles this for us for _, cidr := range append(gw.GetRoutesIPv4(), gw.GetRoutesIPv6()...) { - if strings.HasPrefix(cidr, TunnelNetworkPrefix) { - // Don't add routes for the tunnel network, as the whole /21 net is already routed to utun + if IsTunnelRoute(tunnelNet, cidr) { + c.log.WithFields(logrus.Fields{ + "cidr": cidr, + "gateway": gw.GetName(), + }).Debug("skipping tunnel route") continue } - cidr = strings.TrimSpace(cidr) - - cmd := exec.CommandContext( - ctx, - "ip", - "route", - "add", - cidr, - "dev", - c.helperConfig.Interface, - ) - output, err := cmd.CombinedOutput() - if exitErr, ok := err.(*exec.ExitError); ok { - if exitErr.ExitCode() == 2 && strings.Contains(string(output), "File exists") { + prefix, err := iputil.ParsePrefix(cidr) + if err != nil { + return routesAdded, fmt.Errorf("parse route: %w", err) + } + addr := prefix.Addr() + ones := prefix.Bits() + var bits int + if addr.Is4() { + bits = 32 + } else { + bits = 128 + } + dst := &net.IPNet{ + IP: addr.AsSlice(), + Mask: net.CIDRMask(ones, bits), + } + + route := &netlink.Route{ + LinkIndex: link.Attrs().Index, + Dst: dst, + } + + if err := netlink.RouteAdd(route); err != nil { + if errors.Is(err, syscall.EEXIST) { + c.log.WithFields(logrus.Fields{ + "cidr": cidr, + "gateway": gw.GetName(), + }).Debug("route already exists") continue } - return routesAdded, fmt.Errorf("executing %v: %w, stderr: %s", cmd, exitErr, string(output)) + return routesAdded, fmt.Errorf("add route %s: %w", cidr, err) } + c.log.WithFields(logrus.Fields{ + "cidr": cidr, + "gateway": gw.GetName(), + }).Debug("route added") routesAdded++ } } + c.log.WithField("routes_added", routesAdded).Debug("route setup complete") return routesAdded, nil } func (c *LinuxConfigurator) SetupInterface(ctx context.Context, cfg *pb.Configuration) error { + tunnelNet, err := TunnelNetworkFromIP(cfg.DeviceIPv4) + if err != nil { + return fmt.Errorf("derive tunnel network: %w", err) + } + c.mu.Lock() + c.tunnelNet = tunnelNet + c.mu.Unlock() + c.log.WithFields(logrus.Fields{ + "device_ipv4": cfg.DeviceIPv4, + "device_ipv6": cfg.DeviceIPv6, + "tunnel_net": tunnelNet.String(), + "interface": c.helperConfig.Interface, + }).Debug("setting up interface") + if c.interfaceExists(ctx) { + c.log.WithField("interface", c.helperConfig.Interface).Debug("interface already exists, skipping creation") return nil } - commands := [][]string{ - {"ip", "link", "add", "dev", c.helperConfig.Interface, "type", "wireguard"}, - {"ip", "link", "set", "mtu", "1360", "up", "dev", c.helperConfig.Interface}, - {"ip", "address", "add", "dev", c.helperConfig.Interface, cfg.DeviceIPv4 + "/21"}, - {"ip", "address", "add", "dev", c.helperConfig.Interface, cfg.DeviceIPv6 + "/64"}, + wgLink := &netlink.Wireguard{ + LinkAttrs: netlink.LinkAttrs{ + Name: c.helperConfig.Interface, + MTU: wireguardMTU, + }, + } + + c.log.WithFields(logrus.Fields{ + "interface": c.helperConfig.Interface, + "mtu": wireguardMTU, + }).Debug("creating wireguard interface") + if err := netlink.LinkAdd(wgLink); err != nil { + return fmt.Errorf("create wireguard interface: %w", err) + } + + link, err := netlink.LinkByName(c.helperConfig.Interface) + if err != nil { + cleanupErr := netlink.LinkDel(wgLink) + if cleanupErr != nil { + return fmt.Errorf("lookup interface after creation: %w (additionally, failed to delete interface: %v)", err, cleanupErr) + } + return fmt.Errorf("lookup interface after creation: %w", err) + } + c.log.WithFields(logrus.Fields{ + "interface": c.helperConfig.Interface, + "index": link.Attrs().Index, + "type": link.Type(), + }).Debug("interface created") + + // cleanup deletes the interface if any subsequent configuration step fails. + cleanup := func(cause error) error { + if delErr := netlink.LinkDel(link); delErr != nil { + return fmt.Errorf("%w (additionally, failed to delete interface: %v)", cause, delErr) + } + return cause + } + + ipv4Addr, err := netlink.ParseAddr(cfg.DeviceIPv4 + "/21") + if err != nil { + return cleanup(fmt.Errorf("parse IPv4 address: %w", err)) + } + c.log.WithField("address", ipv4Addr.String()).Debug("adding IPv4 address") + if err := netlink.AddrAdd(link, ipv4Addr); err != nil { + return cleanup(fmt.Errorf("add IPv4 address: %w", err)) + } + + if cfg.DeviceIPv6 != "" { + ipv6Addr, err := netlink.ParseAddr(cfg.DeviceIPv6 + "/64") + if err != nil { + return cleanup(fmt.Errorf("parse IPv6 address: %w", err)) + } + c.log.WithField("address", ipv6Addr.String()).Debug("adding IPv6 address") + if err := netlink.AddrAdd(link, ipv6Addr); err != nil { + return cleanup(fmt.Errorf("add IPv6 address: %w", err)) + } } - return runCommands(ctx, commands) + c.log.Debug("bringing interface up") + if err := netlink.LinkSetUp(link); err != nil { + return cleanup(fmt.Errorf("bring interface up: %w", err)) + } + + c.log.WithField("interface", c.helperConfig.Interface).Debug("interface setup complete") + return nil } func (c *LinuxConfigurator) TeardownInterface(ctx context.Context) error { if !c.interfaceExists(ctx) { + c.log.WithField("interface", c.helperConfig.Interface).Debug("interface does not exist, skipping teardown") return nil } - cmd := exec.CommandContext(ctx, "ip", "link", "del", c.helperConfig.Interface) - out, err := cmd.CombinedOutput() + link, err := netlink.LinkByName(c.helperConfig.Interface) if err != nil { - return fmt.Errorf("teardown failed: %w, stderr: %s", err, string(out)) + return fmt.Errorf("lookup interface %q: %w", c.helperConfig.Interface, err) + } + + c.log.WithField("interface", c.helperConfig.Interface).Debug("deleting interface") + if err := netlink.LinkDel(link); err != nil { + return fmt.Errorf("delete interface: %w", err) } + c.log.WithField("interface", c.helperConfig.Interface).Debug("interface deleted") return nil } func (c *LinuxConfigurator) interfaceExists(ctx context.Context) bool { - cmd := exec.CommandContext(ctx, "ip", "link", "show", "dev", c.helperConfig.Interface) - return cmd.Run() == nil + _, err := netlink.LinkByName(c.helperConfig.Interface) + return err == nil } diff --git a/internal/helper/helper_windows.go b/internal/helper/helper_windows.go index eb8e8d25b..17b74cf79 100644 --- a/internal/helper/helper_windows.go +++ b/internal/helper/helper_windows.go @@ -1,135 +1,205 @@ package helper import ( - "bytes" "context" + "errors" "fmt" + "net" + "net/netip" "os" - "os/exec" - "time" - + "sync" + + "github.com/sirupsen/logrus" + "golang.org/x/sys/windows" + "golang.zx2c4.com/wireguard/conn" + "golang.zx2c4.com/wireguard/device" + "golang.zx2c4.com/wireguard/ipc" + "golang.zx2c4.com/wireguard/tun" + "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" + + "github.com/nais/device/internal/iputil" + "github.com/nais/device/internal/wgconfig" "github.com/nais/device/pkg/pb" ) -const wireGuardBinary = `c:\Program Files\WireGuard\wireguard.exe` - type WindowsConfigurator struct { - helperConfig Config - oldWireGuardConfig []byte - wgNeedsRestart bool + helperConfig Config + + mu sync.Mutex + wgDevice *device.Device + tunDev tun.Device + uapi net.Listener + tunnelNet netip.Prefix } var _ OSConfigurator = &WindowsConfigurator{} -func New(helperConfig Config) *WindowsConfigurator { +func New(helperConfig Config, _ *logrus.Entry) *WindowsConfigurator { return &WindowsConfigurator{ helperConfig: helperConfig, } } -func filesExist(files ...string) error { - for _, file := range files { - if err := RegularFileExists(file); err != nil { - return err - } - } - +func (c *WindowsConfigurator) Prerequisites() error { return nil } -func (configurator *WindowsConfigurator) Prerequisites() error { - if err := filesExist(wireGuardBinary); err != nil { - return fmt.Errorf("verifying if file exists: %w", err) - } - - return nil +func (c *WindowsConfigurator) SyncConf(ctx context.Context, cfg *pb.Configuration) error { + return wgconfig.ApplyConfig(ctx, c.helperConfig.Interface, cfg) } -func interfaceExists(ctx context.Context, iface string) bool { - queryService := exec.CommandContext(ctx, "sc", "query", tunnelName(iface)) - if err := queryService.Run(); err != nil { - return false - } else { - return true - } -} +func (c *WindowsConfigurator) SetupRoutes(ctx context.Context, gateways []*pb.Gateway) (int, error) { + c.mu.Lock() + defer c.mu.Unlock() -func (configurator *WindowsConfigurator) SetupInterface(ctx context.Context, cfg *pb.Configuration) error { - if interfaceExists(ctx, configurator.helperConfig.Interface) { - return nil + if c.tunDev == nil { + return 0, fmt.Errorf("TUN device not initialized") } - installService := exec.CommandContext(ctx, wireGuardBinary, "/installtunnelservice", configurator.helperConfig.WireGuardConfigPath) - if b, err := installService.CombinedOutput(); err != nil { - return fmt.Errorf("installing tunnel service: %v: %v", err, string(b)) - } else { - time.Sleep(6 * time.Second) + nativeTun, ok := c.tunDev.(*tun.NativeTun) + if !ok { + return 0, fmt.Errorf("unexpected TUN device type %T", c.tunDev) + } + ifLUID := winipcfg.LUID(nativeTun.LUID()) + + routesAdded := 0 + for _, gw := range gateways { + for _, cidr := range append(gw.GetRoutesIPv4(), gw.GetRoutesIPv6()...) { + if IsTunnelRoute(c.tunnelNet, cidr) { + continue + } + + dst, err := iputil.ParsePrefix(cidr) + if err != nil { + return routesAdded, fmt.Errorf("parse route: %w", err) + } + + nextHop := netip.IPv4Unspecified() + if dst.Addr().Is6() { + nextHop = netip.IPv6Unspecified() + } + + if err := ifLUID.AddRoute(dst, nextHop, 0); err != nil { + if errors.Is(err, windows.ERROR_OBJECT_ALREADY_EXISTS) { + continue + } + return routesAdded, fmt.Errorf("add route %s: %w", cidr, err) + } + routesAdded++ + } } - configurator.wgNeedsRestart = false - - return nil + return routesAdded, nil } -func (configurator *WindowsConfigurator) SetupRoutes(ctx context.Context, gateways []*pb.Gateway) (int, error) { - return 0, nil -} +func (c *WindowsConfigurator) SetupInterface(ctx context.Context, cfg *pb.Configuration) error { + c.mu.Lock() + defer c.mu.Unlock() -func (configurator *WindowsConfigurator) SyncConf(ctx context.Context, cfg *pb.Configuration) error { - newWireGuardConfig, err := os.ReadFile(configurator.helperConfig.WireGuardConfigPath) + tunnelNet, err := TunnelNetworkFromIP(cfg.DeviceIPv4) if err != nil { - return fmt.Errorf("reading WireGuard config file: %w", err) + return fmt.Errorf("derive tunnel network: %w", err) } + c.tunnelNet = tunnelNet - defer func() { - configurator.oldWireGuardConfig = newWireGuardConfig - configurator.wgNeedsRestart = true - }() - - if !configurator.wgNeedsRestart { + if c.wgDevice != nil { return nil } - if fileActuallyChanged(configurator.oldWireGuardConfig, newWireGuardConfig) { - commands := [][]string{ - {"net", "stop", tunnelName(configurator.helperConfig.Interface)}, - {"net", "start", tunnelName(configurator.helperConfig.Interface)}, - } + tunDev, err := tun.CreateTUN(c.helperConfig.Interface, wireguardMTU) + if err != nil { + return fmt.Errorf("create TUN device: %w", err) + } - return runCommands(ctx, commands) + logger := &device.Logger{ + Verbosef: device.DiscardLogf, + Errorf: func(format string, args ...any) { fmt.Fprintf(os.Stderr, "wireguard: "+format+"\n", args...) }, } + wgDev := device.NewDevice(tunDev, conn.NewDefaultBind(), logger) - return nil -} + if err := wgDev.Up(); err != nil { + wgDev.Close() + return fmt.Errorf("bring up wireguard device: %w", err) + } -func (configurator *WindowsConfigurator) TeardownInterface(ctx context.Context) error { - if !interfaceExists(ctx, configurator.helperConfig.Interface) { - return nil + uapi, err := ipc.UAPIListen(c.helperConfig.Interface) + if err != nil { + wgDev.Close() + return fmt.Errorf("listen on UAPI named pipe: %w", err) } - uninstallService := exec.CommandContext(ctx, wireGuardBinary, "/uninstalltunnelservice", configurator.helperConfig.Interface) + c.wgDevice = wgDev + c.tunDev = tunDev + c.uapi = uapi - b, err := uninstallService.CombinedOutput() + nativeTun, ok := tunDev.(*tun.NativeTun) + if !ok { + c.closeLocked() + return fmt.Errorf("unexpected TUN device type %T", tunDev) + } + ifLUID := winipcfg.LUID(nativeTun.LUID()) + + ipv4, err := netip.ParsePrefix(cfg.DeviceIPv4 + "/21") if err != nil { - return fmt.Errorf("uninstalling tunnel service: %v: %v", err, string(b)) + c.closeLocked() + return fmt.Errorf("parse IPv4 address: %w", err) + } + + if err := ifLUID.AddIPAddress(ipv4); err != nil { + c.closeLocked() + return fmt.Errorf("add IPv4 address: %w", err) } - select { - case <-ctx.Done(): - case <-time.After(3 * time.Second): + if cfg.DeviceIPv6 != "" { + ipv6, err := netip.ParsePrefix(cfg.DeviceIPv6 + "/64") + if err != nil { + c.closeLocked() + return fmt.Errorf("parse IPv6 address: %w", err) + } + + if err := ifLUID.AddIPAddress(ipv6); err != nil { + c.closeLocked() + return fmt.Errorf("add IPv6 address: %w", err) + } } + // Start accepting UAPI connections only after all initialization has succeeded. + // This ensures wgctrl can't race against incomplete interface setup. + go func() { + for { + uapiConn, err := uapi.Accept() + if err != nil { + return + } + go wgDev.IpcHandle(uapiConn) + } + }() + return nil } -func tunnelName(interfaceName string) string { - return fmt.Sprintf("WireGuardTunnel$%s", interfaceName) -} +func (c *WindowsConfigurator) TeardownInterface(ctx context.Context) error { + c.mu.Lock() + defer c.mu.Unlock() -func fileActuallyChanged(old, new []byte) bool { - if old == nil || new == nil { - return true + if c.wgDevice == nil { + return nil } - return !bytes.Equal(old, new) + c.closeLocked() + return nil +} + +// closeLocked shuts down the UAPI listener, WireGuard device, and TUN. +// Must be called with c.mu held. +func (c *WindowsConfigurator) closeLocked() { + if c.uapi != nil { + _ = c.uapi.Close() + c.uapi = nil + } + if c.wgDevice != nil { + c.wgDevice.Close() + c.wgDevice = nil + } + c.tunDev = nil } diff --git a/internal/helper/util.go b/internal/helper/util.go index e91c10fc0..9f8785d43 100644 --- a/internal/helper/util.go +++ b/internal/helper/util.go @@ -2,46 +2,42 @@ package helper import ( "archive/zip" - "context" "errors" "fmt" "io" + "net/netip" "os" - "os/exec" "path/filepath" - "time" "github.com/nais/device/internal/ioconvenience" + "github.com/nais/device/internal/iputil" "github.com/sirupsen/logrus" ) const ( - TunnelNetworkPrefix = "10.255.24." + // wireguardMTU is the MTU used for WireGuard tunnel interfaces across all platforms. + wireguardMTU = 1360 + + // tunnelIPv4PrefixLen is the prefix length used for the IPv4 tunnel network. + tunnelIPv4PrefixLen = 21 ) -func RegularFileExists(filepath string) error { - info, err := os.Stat(filepath) +// TunnelNetworkFromIP derives the tunnel network prefix from the device's IPv4 address. +func TunnelNetworkFromIP(deviceIPv4 string) (netip.Prefix, error) { + addr, err := netip.ParseAddr(deviceIPv4) if err != nil { - return err - } - if info.IsDir() { - return fmt.Errorf("%v is a directory", filepath) + return netip.Prefix{}, fmt.Errorf("parse device IPv4 %q: %w", deviceIPv4, err) } - - return nil + return netip.PrefixFrom(addr, tunnelIPv4PrefixLen).Masked(), nil } -func runCommands(ctx context.Context, commands [][]string) error { - for _, s := range commands { - cmd := exec.CommandContext(ctx, s[0], s[1:]...) - - if out, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("running %v: %w: %v", cmd, err, string(out)) - } - - time.Sleep(100 * time.Millisecond) // avoid serializable race conditions with kernel +// IsTunnelRoute reports whether cidr falls within the tunnel network. +func IsTunnelRoute(tunnelNet netip.Prefix, cidr string) bool { + prefix, err := iputil.ParsePrefix(cidr) + if err != nil { + return false } - return nil + return tunnelNet.Contains(prefix.Addr()) && prefix.Bits() >= tunnelNet.Bits() } func ZipLogFiles(files []string, log logrus.FieldLogger) (string, error) { @@ -61,7 +57,7 @@ func ZipLogFiles(files []string, log logrus.FieldLogger) (string, error) { } logFile, err := os.Open(filename) if err != nil { - return "nil", fmt.Errorf("%s %v", filename, err) + return "nil", fmt.Errorf("%s: %w", filename, err) } zipEntryWiter, err := zipWriter.Create(filepath.Base(filename)) if err != nil { diff --git a/internal/integration_test/device_helper_test.go b/internal/integration_test/device_helper_test.go index ae153a48e..0e0e42242 100644 --- a/internal/integration_test/device_helper_test.go +++ b/internal/integration_test/device_helper_test.go @@ -21,7 +21,6 @@ func NewHelper(t *testing.T, log *logrus.Entry, osConfigurator helper.OSConfigur } tempDir, err := os.MkdirTemp(testDir, "naisdevice_helper_test_*") assert.NoError(t, err) - tempfile := filepath.Join(tempDir, "test_interface.conf") t.Cleanup(func() { if t.Failed() { t.Logf("test failed, leaving temp dir in: %v", tempDir) @@ -36,9 +35,8 @@ func NewHelper(t *testing.T, log *logrus.Entry, osConfigurator helper.OSConfigur }) helperConfig := helper.Config{ - Interface: `test_interface`, - LogLevel: logrus.DebugLevel.String(), - WireGuardConfigPath: tempfile, + Interface: `test_interface`, + LogLevel: logrus.DebugLevel.String(), } deviceHelperServer := helper.NewDeviceHelperServer(log, helperConfig, osConfigurator) diff --git a/internal/iputil/iputil.go b/internal/iputil/iputil.go new file mode 100644 index 000000000..cccef27e6 --- /dev/null +++ b/internal/iputil/iputil.go @@ -0,0 +1,26 @@ +// Package iputil provides IP address parsing utilities. +package iputil + +import ( + "fmt" + "net/netip" + "strings" +) + +// ParsePrefix parses s as a CIDR prefix. +// Bare IP addresses without a prefix length are treated as host addresses (/32 or /128). +func ParsePrefix(s string) (netip.Prefix, error) { + s = strings.TrimSpace(s) + + prefix, err := netip.ParsePrefix(s) + if err == nil { + return prefix, nil + } + + addr, addrErr := netip.ParseAddr(s) + if addrErr != nil { + return netip.Prefix{}, fmt.Errorf("parse prefix %q: %w", s, err) + } + + return netip.PrefixFrom(addr, addr.BitLen()), nil +} diff --git a/internal/iputil/iputil_test.go b/internal/iputil/iputil_test.go new file mode 100644 index 000000000..d2a4ada98 --- /dev/null +++ b/internal/iputil/iputil_test.go @@ -0,0 +1,76 @@ +package iputil + +import ( + "net/netip" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParsePrefix(t *testing.T) { + tests := []struct { + name string + input string + want netip.Prefix + wantErr bool + }{ + { + name: "IPv4 CIDR", + input: "10.0.0.0/24", + want: netip.MustParsePrefix("10.0.0.0/24"), + }, + { + name: "IPv4 host CIDR", + input: "10.43.0.60/32", + want: netip.MustParsePrefix("10.43.0.60/32"), + }, + { + name: "bare IPv4 address", + input: "10.43.0.60", + want: netip.MustParsePrefix("10.43.0.60/32"), + }, + { + name: "IPv6 CIDR", + input: "fd01::/64", + want: netip.MustParsePrefix("fd01::/64"), + }, + { + name: "bare IPv6 address", + input: "fd00::1", + want: netip.MustParsePrefix("fd00::1/128"), + }, + { + name: "CIDR with surrounding whitespace", + input: " 10.0.0.0/24 ", + want: netip.MustParsePrefix("10.0.0.0/24"), + }, + { + name: "bare IP with surrounding whitespace", + input: " 10.43.0.60 ", + want: netip.MustParsePrefix("10.43.0.60/32"), + }, + { + name: "garbage input", + input: "not-an-ip", + wantErr: true, + }, + { + name: "empty string", + input: "", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParsePrefix(tt.input) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/internal/wgconfig/config.go b/internal/wgconfig/config.go new file mode 100644 index 000000000..4a6c612e5 --- /dev/null +++ b/internal/wgconfig/config.go @@ -0,0 +1,122 @@ +// Package wgconfig converts protobuf Configuration types to wgtypes.Config +// and applies them via wgctrl.Client.ConfigureDevice(). +package wgconfig + +import ( + "context" + "fmt" + "net" + "time" + + "golang.zx2c4.com/wireguard/wgctrl" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + + "github.com/nais/device/internal/iputil" + "github.com/nais/device/internal/wireguard" + "github.com/nais/device/pkg/pb" +) + +const ( + defaultPersistentKeepalive = 25 * time.Second +) + +// ApplyConfig configures the named WireGuard device with the given configuration. +// It replaces all existing peers. +func ApplyConfig(ctx context.Context, ifaceName string, cfg *pb.Configuration) error { + wgCfg, err := BuildConfig(cfg) + if err != nil { + return fmt.Errorf("build wgctrl config: %w", err) + } + + client, err := wgctrl.New() + if err != nil { + return fmt.Errorf("create wgctrl client: %w", err) + } + defer func() { _ = client.Close() }() + + if err := client.ConfigureDevice(ifaceName, *wgCfg); err != nil { + return fmt.Errorf("configure device %q: %w", ifaceName, err) + } + + return nil +} + +// BuildConfig converts a protobuf Configuration into a wgtypes.Config. +func BuildConfig(cfg *pb.Configuration) (*wgtypes.Config, error) { + privateKey, err := wgtypes.ParseKey(cfg.GetPrivateKey()) + if err != nil { + return nil, fmt.Errorf("parse private key: %w", err) + } + + gateways := cfg.GetGateways() + peers := make([]wgtypes.PeerConfig, 0, len(gateways)) + for _, gw := range gateways { + peerCfg, err := buildPeerConfig(gw) + if err != nil { + return nil, fmt.Errorf("build peer config for %q: %w", gw.GetName(), err) + } + peers = append(peers, *peerCfg) + } + + return &wgtypes.Config{ + PrivateKey: &privateKey, + ReplacePeers: true, + Peers: peers, + }, nil +} + +func buildPeerConfig(peer wireguard.Peer) (*wgtypes.PeerConfig, error) { + publicKey, err := wgtypes.ParseKey(peer.GetPublicKey()) + if err != nil { + return nil, fmt.Errorf("parse public key: %w", err) + } + + allowedIPs, err := parseAllowedIPs(peer.GetAllowedIPs()) + if err != nil { + return nil, fmt.Errorf("parse allowed IPs: %w", err) + } + + peerCfg := &wgtypes.PeerConfig{ + PublicKey: publicKey, + ReplaceAllowedIPs: true, + AllowedIPs: allowedIPs, + } + + if endpoint := peer.GetEndpoint(); endpoint != "" { + addr, err := net.ResolveUDPAddr("udp", endpoint) + if err != nil { + return nil, fmt.Errorf("resolve endpoint %q: %w", endpoint, err) + } + peerCfg.Endpoint = addr + } + + if peer.GetName() == wireguard.PrometheusPeerName { + keepalive := defaultPersistentKeepalive + peerCfg.PersistentKeepaliveInterval = &keepalive + } + + return peerCfg, nil +} + +func parseAllowedIPs(cidrs []string) ([]net.IPNet, error) { + nets := make([]net.IPNet, 0, len(cidrs)) + for _, cidr := range cidrs { + prefix, err := iputil.ParsePrefix(cidr) + if err != nil { + return nil, fmt.Errorf("parse allowed IP: %w", err) + } + addr := prefix.Addr() + ones := prefix.Bits() + var bits int + if addr.Is4() { + bits = 32 + } else { + bits = 128 + } + nets = append(nets, net.IPNet{ + IP: addr.AsSlice(), + Mask: net.CIDRMask(ones, bits), + }) + } + return nets, nil +} diff --git a/internal/wgconfig/config_test.go b/internal/wgconfig/config_test.go new file mode 100644 index 000000000..25038147f --- /dev/null +++ b/internal/wgconfig/config_test.go @@ -0,0 +1,231 @@ +package wgconfig_test + +import ( + "net" + "testing" + "time" + + "github.com/nais/device/internal/wgconfig" + "github.com/nais/device/internal/wireguard" + "github.com/nais/device/pkg/pb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +var testPrivateKey string + +func init() { + key, err := wgtypes.GeneratePrivateKey() + if err != nil { + panic("generate test private key: " + err.Error()) + } + testPrivateKey = key.String() +} + +func generateTestPublicKey() string { + key, err := wgtypes.GeneratePrivateKey() + if err != nil { + panic("generate test key: " + err.Error()) + } + return key.PublicKey().String() +} + +func TestBuildConfig_BasicConversion(t *testing.T) { + gwPubKey := generateTestPublicKey() + + cfg := &pb.Configuration{ + PrivateKey: testPrivateKey, + DeviceIPv4: "10.255.24.1", + DeviceIPv6: "fd00::1", + Gateways: []*pb.Gateway{ + { + Name: "test-gw", + PublicKey: gwPubKey, + Endpoint: "1.2.3.4:51820", + Ipv4: "10.255.24.10", + Ipv6: "fd00::10", + RoutesIPv4: []string{"10.0.0.0/24", "172.16.0.0/16"}, + RoutesIPv6: []string{"fd01::/64"}, + }, + }, + } + + wgCfg, err := wgconfig.BuildConfig(cfg) + require.NoError(t, err) + + expectedPrivKey, _ := wgtypes.ParseKey(testPrivateKey) + assert.Equal(t, expectedPrivKey, *wgCfg.PrivateKey) + assert.True(t, wgCfg.ReplacePeers) + require.Len(t, wgCfg.Peers, 1) + + peer := wgCfg.Peers[0] + + expectedPubKey, _ := wgtypes.ParseKey(gwPubKey) + assert.Equal(t, expectedPubKey, peer.PublicKey) + + assert.Equal(t, "1.2.3.4", peer.Endpoint.IP.String()) + assert.Equal(t, 51820, peer.Endpoint.Port) + + expectedCIDRs := []string{ + "10.0.0.0/24", + "172.16.0.0/16", + "10.255.24.10/32", + "fd00::10/128", + "fd01::/64", + } + require.Len(t, peer.AllowedIPs, len(expectedCIDRs)) + for i, cidr := range expectedCIDRs { + _, expected, _ := net.ParseCIDR(cidr) + assert.Equal(t, *expected, peer.AllowedIPs[i], "AllowedIP mismatch at index %d", i) + } + + assert.Nil(t, peer.PersistentKeepaliveInterval) + assert.True(t, peer.ReplaceAllowedIPs) +} + +func TestBuildConfig_PrometheusPeerKeepalive(t *testing.T) { + promPubKey := generateTestPublicKey() + + cfg := &pb.Configuration{ + PrivateKey: testPrivateKey, + Gateways: []*pb.Gateway{ + { + Name: wireguard.PrometheusPeerName, + PublicKey: promPubKey, + Endpoint: "5.6.7.8:51820", + Ipv4: "10.255.24.20", + }, + }, + } + + wgCfg, err := wgconfig.BuildConfig(cfg) + require.NoError(t, err) + + require.Len(t, wgCfg.Peers, 1) + peer := wgCfg.Peers[0] + + require.NotNil(t, peer.PersistentKeepaliveInterval) + assert.Equal(t, 25*time.Second, *peer.PersistentKeepaliveInterval) +} + +func TestBuildConfig_MultiplePeers(t *testing.T) { + cfg := &pb.Configuration{ + PrivateKey: testPrivateKey, + Gateways: []*pb.Gateway{ + { + Name: "gw-1", + PublicKey: generateTestPublicKey(), + Endpoint: "1.1.1.1:51820", + Ipv4: "10.255.24.10", + }, + { + Name: "gw-2", + PublicKey: generateTestPublicKey(), + Endpoint: "2.2.2.2:51820", + Ipv4: "10.255.24.11", + }, + { + Name: "gw-3", + PublicKey: generateTestPublicKey(), + Endpoint: "3.3.3.3:51820", + Ipv4: "10.255.24.12", + }, + }, + } + + wgCfg, err := wgconfig.BuildConfig(cfg) + require.NoError(t, err) + assert.Len(t, wgCfg.Peers, 3) +} + +func TestBuildConfig_PeerWithoutEndpoint(t *testing.T) { + cfg := &pb.Configuration{ + PrivateKey: testPrivateKey, + Gateways: []*pb.Gateway{ + { + Name: "no-endpoint", + PublicKey: generateTestPublicKey(), + Ipv4: "10.255.24.10", + }, + }, + } + + wgCfg, err := wgconfig.BuildConfig(cfg) + require.NoError(t, err) + require.Len(t, wgCfg.Peers, 1) + assert.Nil(t, wgCfg.Peers[0].Endpoint) +} + +func TestBuildConfig_BareIPWithoutCIDRPrefix(t *testing.T) { + cfg := &pb.Configuration{ + PrivateKey: testPrivateKey, + Gateways: []*pb.Gateway{ + { + Name: "gw-bare-ip", + PublicKey: generateTestPublicKey(), + Endpoint: "1.2.3.4:51820", + Ipv4: "10.255.24.10", + Ipv6: "fd00::10", + RoutesIPv4: []string{"10.43.0.60", "10.0.0.0/24"}, + RoutesIPv6: []string{"fd00::1"}, + }, + }, + } + + wgCfg, err := wgconfig.BuildConfig(cfg) + require.NoError(t, err) + require.Len(t, wgCfg.Peers, 1) + + peer := wgCfg.Peers[0] + expectedCIDRs := []string{ + "10.43.0.60/32", + "10.0.0.0/24", + "10.255.24.10/32", + "fd00::10/128", + "fd00::1/128", + } + require.Len(t, peer.AllowedIPs, len(expectedCIDRs)) + for i, cidr := range expectedCIDRs { + _, expected, _ := net.ParseCIDR(cidr) + assert.Equal(t, *expected, peer.AllowedIPs[i], "AllowedIP mismatch at index %d", i) + } +} + +func TestBuildConfig_InvalidPrivateKey(t *testing.T) { + cfg := &pb.Configuration{ + PrivateKey: "not-a-valid-key", + } + + _, err := wgconfig.BuildConfig(cfg) + assert.Error(t, err) + assert.Contains(t, err.Error(), "parse private key") +} + +func TestBuildConfig_InvalidPeerPublicKey(t *testing.T) { + cfg := &pb.Configuration{ + PrivateKey: testPrivateKey, + Gateways: []*pb.Gateway{ + { + Name: "bad-key", + PublicKey: "invalid", + Ipv4: "10.0.0.1", + }, + }, + } + + _, err := wgconfig.BuildConfig(cfg) + assert.Error(t, err) + assert.Contains(t, err.Error(), "parse public key") +} + +func TestBuildConfig_NoGateways(t *testing.T) { + cfg := &pb.Configuration{ + PrivateKey: testPrivateKey, + } + + wgCfg, err := wgconfig.BuildConfig(cfg) + require.NoError(t, err) + assert.Empty(t, wgCfg.Peers) + assert.True(t, wgCfg.ReplacePeers) +} diff --git a/internal/wireguard/interfaces.go b/internal/wireguard/interfaces.go deleted file mode 100644 index 62ca67f9e..000000000 --- a/internal/wireguard/interfaces.go +++ /dev/null @@ -1,15 +0,0 @@ -package wireguard - -type NetworkConfigurer interface { - ApplyWireGuardConfig(peers []Peer) error - ForwardRoutesV4(routes []string) error - ForwardRoutesV6(routes []string) error - SetupInterface() error - SetupIPTables() error -} - -type IPTables interface { - AppendUnique(table, chain string, rulespec ...string) error - NewChain(table, chain string) error - ChangePolicy(table, chain, target string) error -} diff --git a/internal/wireguard/iptables_linux.go b/internal/wireguard/iptables.go similarity index 100% rename from internal/wireguard/iptables_linux.go rename to internal/wireguard/iptables.go diff --git a/internal/wireguard/keys.go b/internal/wireguard/keys.go index 23500d0de..229de84c4 100644 --- a/internal/wireguard/keys.go +++ b/internal/wireguard/keys.go @@ -1,83 +1,62 @@ package wireguard import ( - "crypto/rand" - "encoding/base64" "errors" "fmt" "io/fs" "os" "path/filepath" + "strings" "github.com/sirupsen/logrus" - "golang.org/x/crypto/curve25519" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -type PrivateKey []byte - -// Public returns the public key base64 encoded -func (p PrivateKey) Public() []byte { - return keyToBase64(pubKey(p)) -} - -// Private returns the private key base64 encoded -func (p PrivateKey) Private() []byte { - return keyToBase64(p) -} - -func GenKey() (PrivateKey, error) { - var privateKey [32]byte - - n, err := rand.Read(privateKey[:]) - - if err != nil || n != len(privateKey) { - return nil, fmt.Errorf("unable to generate random bytes") - } - - privateKey[0] &= 248 - privateKey[31] = (privateKey[31] & 127) | 64 - return PrivateKey(privateKey[:]), nil -} - -func pubKey(privateKeySlice []byte) []byte { - var privateKey [32]byte - var publicKey [32]byte - copy(privateKey[:], privateKeySlice[:]) - - curve25519.ScalarBaseMult(&publicKey, &privateKey) - - return publicKey[:] -} - -func keyToBase64(key []byte) []byte { - dst := make([]byte, base64.StdEncoding.EncodedLen(len(key))) - base64.StdEncoding.Encode(dst, key) - return dst -} - -func ReadOrCreatePrivateKey(path string, log *logrus.Entry) (PrivateKey, error) { +func ReadOrCreatePrivateKey(path string, log *logrus.Entry) (wgtypes.Key, error) { b, err := os.ReadFile(path) if err != nil && !errors.Is(err, fs.ErrNotExist) { - return nil, fmt.Errorf("read private key: %w", err) + return wgtypes.Key{}, fmt.Errorf("read private key: %w", err) } if errors.Is(err, fs.ErrNotExist) { log.Info("no private key found, generating new one...") - b, err = GenKey() + key, err := wgtypes.GeneratePrivateKey() if err != nil { - return nil, fmt.Errorf("generate private key: %w", err) + return wgtypes.Key{}, fmt.Errorf("generate private key: %w", err) } if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { - return nil, fmt.Errorf("create config dir: %w", err) + return wgtypes.Key{}, fmt.Errorf("create config dir: %w", err) } - if err := os.WriteFile(path, b, 0o600); err != nil { - return nil, fmt.Errorf("write private key: %w", err) + if err := os.WriteFile(path, []byte(key.String()), 0o600); err != nil { + return wgtypes.Key{}, fmt.Errorf("write private key: %w", err) } - } else { - log.Info("found private key, using it...") + + return key, nil + } + + log.Info("found private key, using it...") + + if key, err := wgtypes.ParseKey(strings.TrimSpace(string(b))); err == nil { + return key, nil + } + + if len(b) != len(wgtypes.Key{}) { + return wgtypes.Key{}, fmt.Errorf("parse private key: invalid key length %d", len(b)) + } + + legacyKey, err := wgtypes.NewKey(b) + if err != nil { + return wgtypes.Key{}, fmt.Errorf("parse legacy private key: %w", err) + } + + if err := os.WriteFile(path, []byte(legacyKey.String()), 0o600); err != nil { + return wgtypes.Key{}, fmt.Errorf("rewrite private key: %w", err) + } + if err := os.Chmod(path, 0o600); err != nil { + return wgtypes.Key{}, fmt.Errorf("set private key file mode: %w", err) } - return PrivateKey(b), nil + return legacyKey, nil } diff --git a/internal/wireguard/keys_test.go b/internal/wireguard/keys_test.go new file mode 100644 index 000000000..0ce4c62ab --- /dev/null +++ b/internal/wireguard/keys_test.go @@ -0,0 +1,54 @@ +package wireguard_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/nais/device/internal/wireguard" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +func TestReadOrCreatePrivateKey_CreatesKeyWhenMissing(t *testing.T) { + dir := t.TempDir() + keyPath := filepath.Join(dir, "private.key") + + key, err := wireguard.ReadOrCreatePrivateKey(keyPath, logrus.New().WithField("test", t.Name())) + require.NoError(t, err) + assert.NotEqual(t, wgtypes.Key{}, key) + + stored, err := os.ReadFile(keyPath) + require.NoError(t, err) + assert.Equal(t, key.String(), string(stored)) + + info, err := os.Stat(keyPath) + require.NoError(t, err) + assert.Equal(t, os.FileMode(0o600), info.Mode().Perm()) +} + +func TestReadOrCreatePrivateKey_MigratesLegacyRawKey(t *testing.T) { + dir := t.TempDir() + keyPath := filepath.Join(dir, "private.key") + + original, err := wgtypes.GeneratePrivateKey() + require.NoError(t, err) + + legacyBytes := make([]byte, len(original)) + copy(legacyBytes, original[:]) + require.NoError(t, os.WriteFile(keyPath, legacyBytes, 0o644)) + + key, err := wireguard.ReadOrCreatePrivateKey(keyPath, logrus.New().WithField("test", t.Name())) + require.NoError(t, err) + assert.Equal(t, original, key) + + migrated, err := os.ReadFile(keyPath) + require.NoError(t, err) + assert.Equal(t, original.String(), string(migrated)) + + info, err := os.Stat(keyPath) + require.NoError(t, err) + assert.Equal(t, os.FileMode(0o600), info.Mode().Perm()) +} diff --git a/internal/wireguard/network_configurer.go b/internal/wireguard/network_configurer.go index 7db2f57b2..093fe5009 100644 --- a/internal/wireguard/network_configurer.go +++ b/internal/wireguard/network_configurer.go @@ -1,5 +1,3 @@ -//go:build linux - package wireguard import ( @@ -15,6 +13,20 @@ import ( "github.com/sirupsen/logrus" ) +type NetworkConfigurer interface { + ApplyWireGuardConfig(peers []Peer) error + ForwardRoutesV4(routes []string) error + ForwardRoutesV6(routes []string) error + SetupInterface() error + SetupIPTables() error +} + +type IPTables interface { + AppendUnique(table, chain string, rulespec ...string) error + NewChain(table, chain string) error + ChangePolicy(table, chain, target string) error +} + type subNetworkConfigurer struct { ip *netip.Prefix iface *net.Interface diff --git a/internal/wireguard/wireguardpeerconfig.go b/internal/wireguard/wireguardpeerconfig.go deleted file mode 100644 index 3ce34a6ce..000000000 --- a/internal/wireguard/wireguardpeerconfig.go +++ /dev/null @@ -1,9 +0,0 @@ -package wireguard - -import "io" - -type WireGuardPeerConfig interface { - GetTunnelIP() string - GetWireGuardConfigPath() string - WriteWireGuardBase(io.Writer) error -} diff --git a/mise/config.toml b/mise/config.toml index 13c24a7fb..e0c29096d 100644 --- a/mise/config.toml +++ b/mise/config.toml @@ -1,14 +1,7 @@ [tools] buf = "1.59.0" git-cliff = "2.12.0" -go = """ -{%- set gomod = read_file(path=config_root ~ "/go.mod") -%} -{%- for line in gomod | split(pat="\n") -%} -{%- if line is starting_with("go ") -%} -{{- line | replace(from="go ", to="") | trim -}} -{%- endif -%} -{%- endfor -%} -""" +go = "1.26.1" jq = "1.8.1" protoc = "34.1" protoc-gen-go = "1.36.9" diff --git a/mise/tasks/build/windows.sh b/mise/tasks/build/windows.sh index 614ab871a..567411f99 100755 --- a/mise/tasks/build/windows.sh +++ b/mise/tasks/build/windows.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash #MISE description="Build windows client" +#MISE depends=["build:windows:verify-wintun"] #MISE env={ GOOS = "windows" } set -o errexit @@ -17,3 +18,5 @@ GOOS="" GOARCH="" go tool github.com/akavel/rsrc -arch "$REALGOARCH" -ico assets go build -o bin/windows-client/naisdevice-systray.exe --tags "$gotags" -ldflags "-s $ldflags -H=windowsgui" ./cmd/naisdevice-systray go build -o bin/windows-client/naisdevice-agent.exe --tags "$gotags" -ldflags "-s $ldflags -H=windowsgui" ./cmd/naisdevice-agent go build -o bin/windows-client/naisdevice-helper.exe --tags "$gotags" -ldflags "-s $ldflags" ./cmd/naisdevice-helper + +cp "${MISE_PROJECT_ROOT}/assets/windows/wintun-${REALGOARCH}.dll" ./bin/windows-client/wintun.dll diff --git a/mise/tasks/build/windows/update-wintun.sh b/mise/tasks/build/windows/update-wintun.sh new file mode 100755 index 000000000..c8ab101a5 --- /dev/null +++ b/mise/tasks/build/windows/update-wintun.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +#MISE description="Update bundled wintun DLLs" + +set -o errexit +set -o pipefail +set -o nounset + +MISE_PROJECT_ROOT="${MISE_PROJECT_ROOT:-$(pwd)}" + +version="${1:?usage: mise run build:windows:update-wintun }" +if [[ "$version" =~ ^v ]]; then + version="${version#v}" +fi + +base_url="https://www.wintun.net" +zip_rel_path="builds/wintun-${version}.zip" +zip_url="${base_url}/${zip_rel_path}" +manifest_url="${base_url}/" + +workdir="$(mktemp -d)" +trap 'rm -rf "$workdir"' EXIT + +dst_dir="${MISE_PROJECT_ROOT}/assets/windows" +manifest="${dst_dir}/wintun.sha256" + +curl -fsSL "$manifest_url" -o "$workdir/index.html" + +zip_sha="$(sed -nE 's/.*SHA2-256: ([0-9a-f]{64})<\/code>.*/\1/p' "$workdir/index.html" | head -n 1)" +if [[ -z "$zip_sha" ]]; then + echo "failed to read wintun zip SHA256 from ${manifest_url}" >&2 + exit 1 +fi + +declared_zip_path="$(grep -Eo 'builds/wintun-[0-9]+\.[0-9]+\.[0-9]+\.zip' "$workdir/index.html" | head -n 1)" +if [[ -n "$declared_zip_path" && "$declared_zip_path" != "$zip_rel_path" ]]; then + echo "requested wintun version ${version}, but website currently publishes ${declared_zip_path}" >&2 + exit 1 +fi + +curl -fsSL "$zip_url" -o "$workdir/wintun.zip" + +actual_zip_sha="$(shasum -a 256 "$workdir/wintun.zip" | awk '{print $1}')" +if [[ "$actual_zip_sha" != "$zip_sha" ]]; then + echo "wintun zip checksum mismatch: got $actual_zip_sha, want $zip_sha" >&2 + exit 1 +fi + +unzip -q "$workdir/wintun.zip" -d "$workdir" + +install -m 0644 "$workdir/wintun/bin/amd64/wintun.dll" "${dst_dir}/wintun-amd64.dll" +install -m 0644 "$workdir/wintun/bin/arm64/wintun.dll" "${dst_dir}/wintun-arm64.dll" + +amd64_sha="$(shasum -a 256 "${dst_dir}/wintun-amd64.dll" | awk '{print $1}')" +arm64_sha="$(shasum -a 256 "${dst_dir}/wintun-arm64.dll" | awk '{print $1}')" + +{ + echo "${amd64_sha} wintun-amd64.dll" + echo "${arm64_sha} wintun-arm64.dll" +} >"${manifest}" + +cat "${manifest}" diff --git a/mise/tasks/build/windows/verify-wintun.sh b/mise/tasks/build/windows/verify-wintun.sh new file mode 100755 index 000000000..d8dce007a --- /dev/null +++ b/mise/tasks/build/windows/verify-wintun.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +#MISE description="Verify wintun checksums" +#MISE env={ GOOS = "windows" } + +set -o errexit +set -o pipefail +set -o nounset + +readonly WINTUN_MANIFEST="${MISE_PROJECT_ROOT}/assets/windows/wintun.sha256" + +verify_wintun() { + local arch dll expected actual + arch="$1" + dll="${MISE_PROJECT_ROOT}/assets/windows/wintun-${arch}.dll" + + if [[ ! -f "$dll" ]]; then + echo "wintun DLL not found: $dll" >&2 + exit 1 + fi + + expected="$(awk '$2 == "wintun-'"${arch}"'.dll" {print $1}' "$WINTUN_MANIFEST")" + if [[ -z "$expected" ]]; then + echo "No checksum entry for wintun-${arch}.dll in $WINTUN_MANIFEST" >&2 + exit 1 + fi + + actual="$(shasum -a 256 "$dll" | awk '{print $1}')" + if [[ "$actual" != "$expected" ]]; then + echo "wintun checksum mismatch for $arch: got $actual, want $expected" >&2 + exit 1 + fi +} + +REALGOARCH="${GOARCH:-amd64}" +verify_wintun "$REALGOARCH" diff --git a/mise/tasks/ci/release-info.sh b/mise/tasks/ci/release-info.sh index 3d3dc64cb..d09e4f238 100755 --- a/mise/tasks/ci/release-info.sh +++ b/mise/tasks/ci/release-info.sh @@ -48,6 +48,10 @@ main() { return fi + if [[ "${PRE_RELEASE:-}" == "true" && -n "${PR_NUMBER:-}" ]]; then + version="${version}-rc.${PR_NUMBER}" + fi + output "changelog" "$changelog" output "version" "$version" } diff --git a/mise/tasks/clean.sh b/mise/tasks/clean.sh index 84a35236f..68991cc53 100755 --- a/mise/tasks/clean.sh +++ b/mise/tasks/clean.sh @@ -2,7 +2,5 @@ #MISE description="Clean up build artifacts" rm -f ./*.deb ./*.pkg rm -rf ./*.app -rm -rf wireguard-go-* -rm -rf wireguard-tools-* rm -f ./packaging/windows/naisdevice*.exe rm -rf ./bin diff --git a/mise/tasks/package/linux.sh b/mise/tasks/package/linux.sh index f2866876e..35e03de66 100755 --- a/mise/tasks/package/linux.sh +++ b/mise/tasks/package/linux.sh @@ -16,7 +16,9 @@ outfile="$OUTFILE" name=$(basename "$outfile" | cut -d '_' -f 1) arch="$GOARCH" -VERSION="1:${VERSION#v}" NAME="$name" ARCH="$arch" GOARCH="" go tool github.com/goreleaser/nfpm/v2/cmd/nfpm package \ +deb_version="${VERSION#v}" +deb_version="${deb_version//-/\~}" # convert semver pre-release '-' to debian '~' so pre-releases sort lower +VERSION="1:${deb_version}" NAME="$name" ARCH="$arch" GOARCH="" go tool github.com/goreleaser/nfpm/v2/cmd/nfpm package \ --packager deb \ --config "./assets/linux/nfpm.yaml" \ --target "$outfile" diff --git a/mise/tasks/package/windows.sh b/mise/tasks/package/windows.sh index d19435ad5..7ff6d45e1 100755 --- a/mise/tasks/package/windows.sh +++ b/mise/tasks/package/windows.sh @@ -8,7 +8,12 @@ set -o nounset # Windows MSI requires X.X.X.X version format # shellcheck disable=SC2153 -version="${VERSION#"v"}.0" +version="${VERSION#"v"}" +if [[ "$version" =~ ^([0-9]+\.[0-9]+\.[0-9]+)-rc\.([0-9]+)$ ]]; then + version="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}" +else + version="${version%%-*}.0" +fi # shellcheck disable=SC2153 outfile=$OUTFILE # shellcheck disable=SC2153 @@ -36,7 +41,5 @@ else sign_flags="" fi -wireguard="${MISE_PROJECT_ROOT}/assets/windows/wireguard-${GOARCH}-0.5.3.msi" -wireguard_filename=$(basename "$wireguard") # shellcheck disable=SC2086 -makensis -NOCD "-DWIREGUARD=$wireguard" "-DWIREGUARD_FILENAME=$wireguard_filename" "-DOUTFILE=$outfile" "-DVERSION=$version" $sign_flags ./assets/windows/naisdevice.nsi +makensis -NOCD "-DOUTFILE=$outfile" "-DVERSION=$version" $sign_flags ./assets/windows/naisdevice.nsi diff --git a/mise/tasks/smoke-test/linux.sh b/mise/tasks/smoke-test/linux.sh new file mode 100755 index 000000000..2ed98ff13 --- /dev/null +++ b/mise/tasks/smoke-test/linux.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +#MISE description="Run smoke test on Linux using the .deb installer" + +set -o errexit +set -o pipefail +set -o nounset + +installer_deb="${1:?usage: mise run smoke-test:linux }" + +cleanup() { + echo "Cleaning up..." + sudo dpkg -r naisdevice 2>/dev/null || true +} +trap cleanup EXIT + +echo "==> Building smoke-test binary" +go build -o ./smoke-test ./cmd/smoke-test + +echo "==> Installing dependencies" +sudo apt-get update && sudo apt-get install --yes jq + +echo "==> Installing $installer_deb" +sudo apt-get install --yes "$installer_deb" + +echo "==> Waiting for helper to start" +for i in $(seq 1 10); do + if sudo test -S /run/naisdevice/helper.sock; then + echo "helper is running" + break + fi + if [ "$i" -eq 10 ]; then + echo "helper socket not found after install" + sudo journalctl -u naisdevice-helper.service --no-pager -n 50 || true + exit 1 + fi + sleep 3 +done + +echo "==> Running smoke test" +sudo ./smoke-test +echo "==> Smoke test passed" diff --git a/mise/tasks/smoke-test/macos.sh b/mise/tasks/smoke-test/macos.sh new file mode 100755 index 000000000..94e7c7418 --- /dev/null +++ b/mise/tasks/smoke-test/macos.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +#MISE description="Run smoke test on macOS using the .pkg installer" + +set -o errexit +set -o pipefail +set -o nounset + +installer_pkg="${1:?usage: mise run smoke-test:macos }" + +cleanup() { + echo "Cleaning up..." + sudo launchctl unload /Library/LaunchDaemons/io.nais.device.helper.plist 2>/dev/null || true + sudo rm -rf /Applications/naisdevice.app + sudo rm -f /Library/LaunchDaemons/io.nais.device.helper.plist +} +trap cleanup EXIT + +echo "==> Building smoke-test binary" +go build -o ./smoke-test ./cmd/smoke-test + +echo "==> Installing $installer_pkg" +sudo installer -pkg "$installer_pkg" -target / + +echo "==> Waiting for helper to start" +for i in $(seq 1 10); do + if sudo test -S /var/run/naisdevice/helper.sock; then + echo "helper is running" + break + fi + if [ "$i" -eq 10 ]; then + echo "helper socket not found after install" + cat /Library/Logs/device-agent-helper-*.log 2>/dev/null || true + exit 1 + fi + sleep 3 +done + +echo "==> Running smoke test" +sudo ./smoke-test +echo "==> Smoke test passed" diff --git a/mise/tasks/smoke-test/windows.sh b/mise/tasks/smoke-test/windows.sh new file mode 100755 index 000000000..094813f1e --- /dev/null +++ b/mise/tasks/smoke-test/windows.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +#MISE description="Run smoke test on Windows using the NSIS installer" + +set -o errexit +set -o pipefail +set -o nounset + +installer_exe="${1:?usage: mise run smoke-test:windows }" +install_dir="${PROGRAMFILES:-C:\\Program Files}\\NAV\\naisdevice" + +cleanup() { + echo "Cleaning up..." + uninstaller="$install_dir\\uninstaller.exe" + if [ -f "$uninstaller" ]; then + powershell.exe -Command "Start-Process -FilePath '$uninstaller' -ArgumentList '/S' -Wait" + echo "Uninstaller completed" + fi +} +trap cleanup EXIT + +echo "==> Building smoke-test binary" +go build -o ./smoke-test.exe ./cmd/smoke-test + +echo "==> Running installer" +powershell.exe -Command "Start-Process -FilePath '$(cygpath -w "$installer_exe")' -ArgumentList '/S' -Wait" + +echo "==> Verifying files installed" +if [ ! -f "$install_dir/naisdevice-helper.exe" ]; then + echo "naisdevice-helper.exe not found in $install_dir" + exit 1 +fi +echo "Binary installed at $install_dir" + +echo "==> Waiting for service to start" +for i in $(seq 1 10); do + status=$(powershell.exe -Command "(Get-Service -Name NaisDeviceHelper -ErrorAction SilentlyContinue).Status" | tr -d '\r') + if [ "$status" = "Running" ]; then + echo "NaisDeviceHelper is running" + break + fi + if [ "$i" -eq 10 ]; then + echo "Service is not running after waiting: $status" + log_dir="${PROGRAMDATA:-C:\\ProgramData}\\NAV\\naisdevice\\logs" + if [ -d "$log_dir" ]; then + for f in "$log_dir"/*; do + echo "--- $(basename "$f") ---" + tail -50 "$f" 2>/dev/null || true + done + fi + exit 1 + fi + sleep 3 +done + +echo "==> Running smoke test" +./smoke-test.exe +echo "==> Smoke test passed" diff --git a/packaging/nix/naisdevice/module.nix b/packaging/nix/naisdevice/module.nix index ec3e3c658..74f453604 100644 --- a/packaging/nix/naisdevice/module.nix +++ b/packaging/nix/naisdevice/module.nix @@ -22,10 +22,6 @@ in { systemd.services.naisdevice-helper = { description = "naisdevice-helper service"; wantedBy = ["multi-user.target"]; - path = [ - pkgs.wireguard-tools - pkgs.iproute2 - ]; serviceConfig.ExecStart = "${cfg.package}/bin/naisdevice-helper"; serviceConfig.Restart = "always"; };