From f9845e53a03d818cbfb0cd3b86bc96269a3c9574 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Thu, 4 Jul 2024 16:50:07 +0200
Subject: [PATCH 01/77] Sort routes by ID and remove DNS routes from
overlapping list (#2234)
---
client/ui/route.go | 28 +++++++++++++++++++++-------
1 file changed, 21 insertions(+), 7 deletions(-)
diff --git a/client/ui/route.go b/client/ui/route.go
index 02012df1a3d..5b6b8fee0d8 100644
--- a/client/ui/route.go
+++ b/client/ui/route.go
@@ -4,6 +4,7 @@ package main
import (
"fmt"
+ "sort"
"strings"
"time"
@@ -24,6 +25,7 @@ const (
allRoutes filter = "all"
overlappingRoutes filter = "overlapping"
exitNodeRoutes filter = "exit-node"
+ getClientFMT = "get client: %v"
)
type filter string
@@ -95,6 +97,8 @@ func (s *serviceClient) updateRoutes(grid *fyne.Container, f filter) {
return
}
+ sortRoutesByIDs(filteredRoutes)
+
for _, route := range filteredRoutes {
r := route
@@ -150,8 +154,8 @@ func (s *serviceClient) updateRoutes(grid *fyne.Container, f filter) {
func (s *serviceClient) getFilteredRoutes(f filter) ([]*proto.Route, error) {
routes, err := s.fetchRoutes()
if err != nil {
- log.Errorf("get client: %v", err)
- s.showError(fmt.Errorf("get client: %v", err))
+ log.Errorf(getClientFMT, err)
+ s.showError(fmt.Errorf(getClientFMT, err))
return nil, err
}
switch f {
@@ -168,6 +172,9 @@ func getOverlappingRoutes(routes []*proto.Route) []*proto.Route {
var filteredRoutes []*proto.Route
existingRange := make(map[string][]*proto.Route)
for _, route := range routes {
+ if len(route.Domains) > 0 {
+ continue
+ }
if r, exists := existingRange[route.GetNetwork()]; exists {
r = append(r, route)
existingRange[route.GetNetwork()] = r
@@ -192,10 +199,17 @@ func getExitNodeRoutes(routes []*proto.Route) []*proto.Route {
}
return filteredRoutes
}
+
+func sortRoutesByIDs(routes []*proto.Route) {
+ sort.Slice(routes, func(i, j int) bool {
+ return strings.ToLower(routes[i].GetID()) < strings.ToLower(routes[j].GetID())
+ })
+}
+
func (s *serviceClient) fetchRoutes() ([]*proto.Route, error) {
conn, err := s.getSrvClient(defaultFailTimeout)
if err != nil {
- return nil, fmt.Errorf("get client: %v", err)
+ return nil, fmt.Errorf(getClientFMT, err)
}
resp, err := conn.ListRoutes(s.ctx, &proto.ListRoutesRequest{})
@@ -209,8 +223,8 @@ func (s *serviceClient) fetchRoutes() ([]*proto.Route, error) {
func (s *serviceClient) selectRoute(id string, checked bool) {
conn, err := s.getSrvClient(defaultFailTimeout)
if err != nil {
- log.Errorf("get client: %v", err)
- s.showError(fmt.Errorf("get client: %v", err))
+ log.Errorf(getClientFMT, err)
+ s.showError(fmt.Errorf(getClientFMT, err))
return
}
@@ -239,7 +253,7 @@ func (s *serviceClient) selectRoute(id string, checked bool) {
func (s *serviceClient) selectAllFilteredRoutes(f filter) {
conn, err := s.getSrvClient(defaultFailTimeout)
if err != nil {
- log.Errorf("get client: %v", err)
+ log.Errorf(getClientFMT, err)
return
}
@@ -256,7 +270,7 @@ func (s *serviceClient) selectAllFilteredRoutes(f filter) {
func (s *serviceClient) deselectAllFilteredRoutes(f filter) {
conn, err := s.getSrvClient(defaultFailTimeout)
if err != nil {
- log.Errorf("get client: %v", err)
+ log.Errorf(getClientFMT, err)
return
}
From 85e991ff789214b3d5954a39e0a8c1e469c8bfe2 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Thu, 4 Jul 2024 19:15:59 +0200
Subject: [PATCH 02/77] Fix issue with canceled context before pushing metrics
and decreasing pushing interval (#2235)
Fix a bug where the post context was canceled before sending metrics to the server.
The interval time was decreased, and an optional environment variable NETBIRD_METRICS_INTERVAL_IN_SECONDS was added to control the interval time.
* update doc URL
---
management/server/metrics/selfhosted.go | 34 +++++++++++++++++++------
1 file changed, 26 insertions(+), 8 deletions(-)
diff --git a/management/server/metrics/selfhosted.go b/management/server/metrics/selfhosted.go
index 357af6e562f..bdf744d211e 100644
--- a/management/server/metrics/selfhosted.go
+++ b/management/server/metrics/selfhosted.go
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
+ "os"
"sort"
"strings"
"time"
@@ -24,7 +25,7 @@ const (
// payloadEndpoint metrics defaultEndpoint to send anonymous data
payloadEndpoint = "https://metrics.netbird.io"
// defaultPushInterval default interval to push metrics
- defaultPushInterval = 24 * time.Hour
+ defaultPushInterval = 12 * time.Hour
// requestTimeout http request timeout
requestTimeout = 45 * time.Second
)
@@ -82,7 +83,9 @@ func NewWorker(ctx context.Context, id string, dataSource DataSource, connManage
// Run runs the metrics worker
func (w *Worker) Run(ctx context.Context) {
- pushTicker := time.NewTicker(defaultPushInterval)
+ interval := getMetricsInterval(ctx)
+
+ pushTicker := time.NewTicker(interval)
for {
select {
case <-w.ctx.Done():
@@ -97,6 +100,20 @@ func (w *Worker) Run(ctx context.Context) {
}
}
+func getMetricsInterval(ctx context.Context) time.Duration {
+ interval := defaultPushInterval
+ if os.Getenv("NETBIRD_METRICS_INTERVAL_IN_SECONDS") != "" {
+ newInterval, err := time.ParseDuration(os.Getenv("NETBIRD_METRICS_INTERVAL_IN_SECONDS") + "s")
+ if err != nil {
+ log.WithContext(ctx).Errorf("unable to parse NETBIRD_METRICS_INTERVAL_IN_SECONDS, using default interval %v. Error: %v", defaultPushInterval, err)
+ } else {
+ log.WithContext(ctx).Infof("using NETBIRD_METRICS_INTERVAL_IN_SECONDS %s", newInterval)
+ interval = newInterval
+ }
+ }
+ return interval
+}
+
func (w *Worker) sendMetrics(ctx context.Context) error {
apiKey, err := getAPIKey(w.ctx)
if err != nil {
@@ -112,10 +129,11 @@ func (w *Worker) sendMetrics(ctx context.Context) error {
httpClient := http.Client{}
- exportJobReq, err := createPostRequest(w.ctx, payloadEndpoint+"/capture/", payloadString)
+ exportJobReq, cancelCTX, err := createPostRequest(w.ctx, payloadEndpoint+"/capture/", payloadString)
if err != nil {
return fmt.Errorf("unable to create metrics post request %v", err)
}
+ defer cancelCTX()
jobResp, err := httpClient.Do(exportJobReq)
if err != nil {
@@ -135,7 +153,7 @@ func (w *Worker) sendMetrics(ctx context.Context) error {
log.WithContext(ctx).Infof("sent anonymous metrics, next push will happen in %s. "+
"You can disable these metrics by running with flag --disable-anonymous-metrics,"+
- " see more information at https://netbird.io/docs/FAQ/metrics-collection", defaultPushInterval)
+ " see more information at https://docs.netbird.io/about-netbird/faq#why-and-what-are-the-anonymous-usage-metrics", getMetricsInterval(ctx))
return nil
}
@@ -373,20 +391,20 @@ func buildMetricsPayload(payload pushPayload) (string, error) {
return string(str), nil
}
-func createPostRequest(ctx context.Context, endpoint string, payloadStr string) (*http.Request, error) {
+func createPostRequest(ctx context.Context, endpoint string, payloadStr string) (*http.Request, context.CancelFunc, error) {
ctx, cancel := context.WithTimeout(ctx, requestTimeout)
- defer cancel()
reqURL := endpoint
payload := strings.NewReader(payloadStr)
req, err := http.NewRequestWithContext(ctx, "POST", reqURL, payload)
if err != nil {
- return nil, err
+ cancel()
+ return nil, nil, err
}
req.Header.Add("content-type", "application/json")
- return req, nil
+ return req, cancel, nil
}
func getMinMaxVersion(inputList []string) (string, string) {
From e8c2fafccd9ebb649a3d15f1b361e09bb2b61639 Mon Sep 17 00:00:00 2001
From: pascal-fischer <32096965+pascal-fischer@users.noreply.github.com>
Date: Wed, 10 Jul 2024 14:08:35 +0200
Subject: [PATCH 03/77] Avoid empty domain overwrite (#2252)
---
management/server/user.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/management/server/user.go b/management/server/user.go
index 302cfccaa9a..266692b8d21 100644
--- a/management/server/user.go
+++ b/management/server/user.go
@@ -937,7 +937,7 @@ func (am *DefaultAccountManager) GetOrCreateAccountByUser(ctx context.Context, u
userObj := account.Users[userID]
- if account.Domain != lowerDomain && userObj.Role == UserRoleOwner {
+ if lowerDomain != "" && account.Domain != lowerDomain && userObj.Role == UserRoleOwner {
account.Domain = lowerDomain
err = am.Store.SaveAccount(ctx, account)
if err != nil {
From f74646a3ac977750241bf46d52abe9e301de2b8f Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Thu, 11 Jul 2024 19:06:55 +0200
Subject: [PATCH 04/77] Add release version to windows binaries and update sign
pipeline version (#2256)
---
.github/workflows/release.yml | 51 +++++++++++++++++++++++------------
versioninfo.json | 1 +
2 files changed, 35 insertions(+), 17 deletions(-)
create mode 100644 versioninfo.json
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 65ae0aa2669..1889b58e740 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -10,8 +10,10 @@ on:
env:
- SIGN_PIPE_VER: "v0.0.11"
+ SIGN_PIPE_VER: "v0.0.12"
GORELEASER_VER: "v1.14.1"
+ PRODUCT_NAME: "NetBird"
+ COPYRIGHT: "Wiretrustee UG (haftungsbeschreankt)"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
@@ -23,6 +25,13 @@ jobs:
env:
flags: ""
steps:
+ - name: Parse semver string
+ id: semver_parser
+ uses: booxmedialtd/ws-action-parse-semver@v1
+ with:
+ input_string: ${{ (startsWith(github.ref, 'refs/tags/v') && github.ref) || 'refs/tags/v0.0.0' }}
+ version_extractor_regex: '\/v(.*)$'
+
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
run: echo "flags=--snapshot" >> $GITHUB_ENV
-
@@ -68,18 +77,18 @@ jobs:
- name: Install OS build dependencies
run: sudo apt update && sudo apt install -y -q gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu
- - name: Install rsrc
- run: go install github.com/akavel/rsrc@v0.10.2
- - name: Generate windows rsrc amd64
- run: rsrc -arch amd64 -ico client/ui/netbird.ico -manifest client/manifest.xml -o client/resources_windows_amd64.syso
- - name: Generate windows rsrc arm64
- run: rsrc -arch arm64 -ico client/ui/netbird.ico -manifest client/manifest.xml -o client/resources_windows_arm64.syso
- - name: Generate windows rsrc arm
- run: rsrc -arch arm -ico client/ui/netbird.ico -manifest client/manifest.xml -o client/resources_windows_arm.syso
- - name: Generate windows rsrc 386
- run: rsrc -arch 386 -ico client/ui/netbird.ico -manifest client/manifest.xml -o client/resources_windows_386.syso
- -
- name: Run GoReleaser
+ - name: Install goversioninfo
+ run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
+ - name: Generate windows syso 386
+ run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-build ${{ github.run_id }} -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -product-version ${{ steps.semver_parser.outputs.fullversion }}.${{ github.run_id }} -o client/resources_windows_386.syso
+ - name: Generate windows syso arm
+ run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-build ${{ github.run_id }} -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -product-version ${{ steps.semver_parser.outputs.fullversion }}.${{ github.run_id }} -o client/resources_windows_arm.syso
+ - name: Generate windows syso arm64
+ run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-build ${{ github.run_id }} -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -product-version ${{ steps.semver_parser.outputs.fullversion }}.${{ github.run_id }} -o client/resources_windows_arm64.syso
+ - name: Generate windows syso amd64
+ run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-build ${{ github.run_id }} -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -product-version ${{ steps.semver_parser.outputs.fullversion }}.${{ github.run_id }} -o client/resources_windows_amd64.syso
+
+ - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
with:
version: ${{ env.GORELEASER_VER }}
@@ -121,6 +130,13 @@ jobs:
release_ui:
runs-on: ubuntu-latest
steps:
+ - name: Parse semver string
+ id: semver_parser
+ uses: booxmedialtd/ws-action-parse-semver@v1
+ with:
+ input_string: ${{ (startsWith(github.ref, 'refs/tags/v') && github.ref) || 'refs/tags/v0.0.0' }}
+ version_extractor_regex: '\/v(.*)$'
+
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
run: echo "flags=--snapshot" >> $GITHUB_ENV
- name: Checkout
@@ -151,10 +167,11 @@ jobs:
- name: Install dependencies
run: sudo apt update && sudo apt install -y -q libappindicator3-dev gir1.2-appindicator3-0.1 libxxf86vm-dev gcc-mingw-w64-x86-64
- - name: Install rsrc
- run: go install github.com/akavel/rsrc@v0.10.2
- - name: Generate windows rsrc
- run: rsrc -arch amd64 -ico client/ui/netbird.ico -manifest client/ui/manifest.xml -o client/ui/resources_windows_amd64.syso
+ - name: Install goversioninfo
+ run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
+ - name: Generate windows syso amd64
+ run: goversioninfo -64 -icon client/ui/netbird.ico -manifest client/ui/manifest.xml -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-build ${{ github.run_id }} -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -product-version ${{ steps.semver_parser.outputs.fullversion }}.${{ github.run_id }} -o client/ui/resources_windows_amd64.syso
+
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
with:
diff --git a/versioninfo.json b/versioninfo.json
new file mode 100644
index 00000000000..0967ef424bc
--- /dev/null
+++ b/versioninfo.json
@@ -0,0 +1 @@
+{}
From 89df6e7242858ec513424835f8cb1788e0c4166c Mon Sep 17 00:00:00 2001
From: Viktor Liu <17948409+lixmal@users.noreply.github.com>
Date: Fri, 12 Jul 2024 08:25:33 +0200
Subject: [PATCH 05/77] Get client ui locale on windows natively (#2251)
---
client/ui/client_ui.go | 85 ------------------------------------
client/ui/font_bsd.go | 26 +++++++++++
client/ui/font_linux.go | 7 +++
client/ui/font_windows.go | 91 +++++++++++++++++++++++++++++++++++++++
4 files changed, 124 insertions(+), 85 deletions(-)
create mode 100644 client/ui/font_bsd.go
create mode 100644 client/ui/font_linux.go
create mode 100644 client/ui/font_windows.go
diff --git a/client/ui/client_ui.go b/client/ui/client_ui.go
index cadd14f1862..67f3ef07bfd 100644
--- a/client/ui/client_ui.go
+++ b/client/ui/client_ui.go
@@ -877,88 +877,3 @@ func checkPIDFile() error {
return os.WriteFile(pidFile, []byte(fmt.Sprintf("%d", os.Getpid())), 0o664) //nolint:gosec
}
-
-func (s *serviceClient) setDefaultFonts() {
- var (
- defaultFontPath string
- )
-
- //TODO: Linux Multiple Language Support
- switch runtime.GOOS {
- case "darwin":
- defaultFontPath = "/Library/Fonts/Arial Unicode.ttf"
- case "windows":
- fontPath := s.getWindowsFontFilePath()
- defaultFontPath = fontPath
- }
-
- _, err := os.Stat(defaultFontPath)
-
- if err == nil {
- os.Setenv("FYNE_FONT", defaultFontPath)
- }
-}
-
-func (s *serviceClient) getWindowsFontFilePath() (fontPath string) {
- /*
- https://learn.microsoft.com/en-us/windows/apps/design/globalizing/loc-international-fonts
- https://learn.microsoft.com/en-us/typography/fonts/windows_11_font_list
- */
-
- var (
- fontFolder string = "C:/Windows/Fonts"
- fontMapping = map[string]string{
- "default": "Segoeui.ttf",
- "zh-CN": "Msyh.ttc",
- "am-ET": "Ebrima.ttf",
- "nirmala": "Nirmala.ttf",
- "chr-CHER-US": "Gadugi.ttf",
- "zh-HK": "Msjh.ttc",
- "zh-TW": "Msjh.ttc",
- "ja-JP": "Yugothm.ttc",
- "km-KH": "Leelawui.ttf",
- "ko-KR": "Malgun.ttf",
- "th-TH": "Leelawui.ttf",
- "ti-ET": "Ebrima.ttf",
- }
- nirMalaLang = []string{
- "as-IN",
- "bn-BD",
- "bn-IN",
- "gu-IN",
- "hi-IN",
- "kn-IN",
- "kok-IN",
- "ml-IN",
- "mr-IN",
- "ne-NP",
- "or-IN",
- "pa-IN",
- "si-LK",
- "ta-IN",
- "te-IN",
- }
- )
- cmd := exec.Command("powershell", "-Command", "(Get-Culture).Name")
- output, err := cmd.Output()
- if err != nil {
- log.Errorf("Failed to get Windows default language setting: %v", err)
- fontPath = path.Join(fontFolder, fontMapping["default"])
- return
- }
- defaultLanguage := strings.TrimSpace(string(output))
-
- for _, lang := range nirMalaLang {
- if defaultLanguage == lang {
- fontPath = path.Join(fontFolder, fontMapping["nirmala"])
- return
- }
- }
-
- if font, ok := fontMapping[defaultLanguage]; ok {
- fontPath = path.Join(fontFolder, font)
- } else {
- fontPath = path.Join(fontFolder, fontMapping["default"])
- }
- return
-}
diff --git a/client/ui/font_bsd.go b/client/ui/font_bsd.go
new file mode 100644
index 00000000000..41bccceca6c
--- /dev/null
+++ b/client/ui/font_bsd.go
@@ -0,0 +1,26 @@
+//go:build darwin || dragonfly || freebsd || netbsd || openbsd
+
+package main
+
+import (
+ "os"
+ "runtime"
+
+ log "github.com/sirupsen/logrus"
+)
+
+const defaultFontPath = "/Library/Fonts/Arial Unicode.ttf"
+
+func (s *serviceClient) setDefaultFonts() {
+ // TODO: add other bsd paths
+ if runtime.GOOS != "darwin" {
+ return
+ }
+
+ if _, err := os.Stat(defaultFontPath); err != nil {
+ log.Errorf("Failed to find default font file: %v", err)
+ return
+ }
+
+ os.Setenv("FYNE_FONT", defaultFontPath)
+}
diff --git a/client/ui/font_linux.go b/client/ui/font_linux.go
new file mode 100644
index 00000000000..4aa92494aa6
--- /dev/null
+++ b/client/ui/font_linux.go
@@ -0,0 +1,7 @@
+//go:build !386
+
+package main
+
+func (s *serviceClient) setDefaultFonts() {
+ //TODO: Linux Multiple Language Support
+}
diff --git a/client/ui/font_windows.go b/client/ui/font_windows.go
new file mode 100644
index 00000000000..c37a5455f0d
--- /dev/null
+++ b/client/ui/font_windows.go
@@ -0,0 +1,91 @@
+package main
+
+import (
+ "os"
+ "path"
+ "unsafe"
+
+ log "github.com/sirupsen/logrus"
+ "golang.org/x/sys/windows"
+)
+
+func (s *serviceClient) setDefaultFonts() {
+ defaultFontPath := s.getWindowsFontFilePath()
+
+ if _, err := os.Stat(defaultFontPath); err != nil {
+ log.Errorf("Failed to find default font file: %v", err)
+ return
+ }
+
+ os.Setenv("FYNE_FONT", defaultFontPath)
+}
+
+func (s *serviceClient) getWindowsFontFilePath() string {
+ var (
+ fontFolder = "C:/Windows/Fonts"
+ fontMapping = map[string]string{
+ "default": "Segoeui.ttf",
+ "zh-CN": "Msyh.ttc",
+ "am-ET": "Ebrima.ttf",
+ "nirmala": "Nirmala.ttf",
+ "chr-CHER-US": "Gadugi.ttf",
+ "zh-HK": "Msjh.ttc",
+ "zh-TW": "Msjh.ttc",
+ "ja-JP": "Yugothm.ttc",
+ "km-KH": "Leelawui.ttf",
+ "ko-KR": "Malgun.ttf",
+ "th-TH": "Leelawui.ttf",
+ "ti-ET": "Ebrima.ttf",
+ }
+ nirMalaLang = []string{
+ "as-IN",
+ "bn-BD",
+ "bn-IN",
+ "gu-IN",
+ "hi-IN",
+ "kn-IN",
+ "kok-IN",
+ "ml-IN",
+ "mr-IN",
+ "ne-NP",
+ "or-IN",
+ "pa-IN",
+ "si-LK",
+ "ta-IN",
+ "te-IN",
+ }
+ )
+
+ // getUserDefaultLocaleName.Call() panics if the func is not found
+ defer func() {
+ if r := recover(); r != nil {
+ log.Errorf("Recovered from panic: %v", r)
+ }
+ }()
+
+ kernel32 := windows.NewLazySystemDLL("kernel32.dll")
+ getUserDefaultLocaleName := kernel32.NewProc("GetUserDefaultLocaleName")
+
+ buf := make([]uint16, 85) // LOCALE_NAME_MAX_LENGTH is usually 85
+ r, _, err := getUserDefaultLocaleName.Call(uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
+ // returns 0 on failure, err is always non-nil
+ // https://learn.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getuserdefaultlocalename
+ if r == 0 {
+ log.Errorf("GetUserDefaultLocaleName call failed: %v", err)
+ return path.Join(fontFolder, fontMapping["default"])
+ }
+
+ defaultLanguage := windows.UTF16ToString(buf)
+
+ for _, lang := range nirMalaLang {
+ if defaultLanguage == lang {
+ return path.Join(fontFolder, fontMapping["nirmala"])
+ }
+ }
+
+ if font, ok := fontMapping[defaultLanguage]; ok {
+ return path.Join(fontFolder, font)
+ }
+
+ return path.Join(fontFolder, fontMapping["default"])
+}
From 1cc341a2684ab538258c20d50013aed82b38dcc0 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 12 Jul 2024 08:52:27 +0200
Subject: [PATCH 06/77] Bump google.golang.org/grpc from 1.64.0 to 1.64.1
(#2248)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.64.0 to 1.64.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.64.0...v1.64.1)
---
updated-dependencies:
- dependency-name: google.golang.org/grpc
dependency-type: direct:production
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
go.mod | 10 +++++-----
go.sum | 20 ++++++++++----------
2 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/go.mod b/go.mod
index 78390042d92..1da44da3bb4 100644
--- a/go.mod
+++ b/go.mod
@@ -19,12 +19,12 @@ require (
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5
github.com/vishvananda/netlink v1.2.1-beta.2
- golang.org/x/crypto v0.23.0
- golang.org/x/sys v0.20.0
+ golang.org/x/crypto v0.24.0
+ golang.org/x/sys v0.21.0
golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
golang.zx2c4.com/wireguard/windows v0.5.3
- google.golang.org/grpc v1.64.0
+ google.golang.org/grpc v1.64.1
google.golang.org/protobuf v1.34.1
gopkg.in/natefinch/lumberjack.v2 v2.0.0
)
@@ -83,10 +83,10 @@ require (
goauthentik.io/api/v3 v3.2023051.3
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
- golang.org/x/net v0.25.0
+ golang.org/x/net v0.26.0
golang.org/x/oauth2 v0.19.0
golang.org/x/sync v0.7.0
- golang.org/x/term v0.20.0
+ golang.org/x/term v0.21.0
google.golang.org/api v0.177.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/postgres v1.5.7
diff --git a/go.sum b/go.sum
index 1a74b266481..8423113444c 100644
--- a/go.sum
+++ b/go.sum
@@ -532,8 +532,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
-golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
-golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
+golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
@@ -577,8 +577,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
-golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
-golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
+golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
@@ -634,8 +634,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
-golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
+golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -643,8 +643,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
-golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
-golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
+golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -701,8 +701,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
-google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
+google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
+google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
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=
From 58fbc1249cc618ba3544942c31c2f3139e0c4729 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Fri, 12 Jul 2024 09:28:53 +0200
Subject: [PATCH 07/77] Fix parameter limit issue for Postgres store (#2261)
Added CreateBatchSize for both SQL stores and updated tests to test large accounts with Postgres, too. Increased the account peer size to 6K.
---
management/server/sql_store.go | 19 ++++++++++---------
management/server/sql_store_test.go | 21 ++++++++++++++++-----
2 files changed, 26 insertions(+), 14 deletions(-)
diff --git a/management/server/sql_store.go b/management/server/sql_store.go
index b5ae8282843..6bcff9f813c 100644
--- a/management/server/sql_store.go
+++ b/management/server/sql_store.go
@@ -662,11 +662,7 @@ func NewSqliteStore(ctx context.Context, dataDir string, metrics telemetry.AppMe
}
file := filepath.Join(dataDir, storeStr)
- db, err := gorm.Open(sqlite.Open(file), &gorm.Config{
- Logger: logger.Default.LogMode(logger.Silent),
- CreateBatchSize: 400,
- PrepareStmt: true,
- })
+ db, err := gorm.Open(sqlite.Open(file), getGormConfig())
if err != nil {
return nil, err
}
@@ -676,10 +672,7 @@ func NewSqliteStore(ctx context.Context, dataDir string, metrics telemetry.AppMe
// NewPostgresqlStore creates a new Postgres store.
func NewPostgresqlStore(ctx context.Context, dsn string, metrics telemetry.AppMetrics) (*SqlStore, error) {
- db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
- Logger: logger.Default.LogMode(logger.Silent),
- PrepareStmt: true,
- })
+ db, err := gorm.Open(postgres.Open(dsn), getGormConfig())
if err != nil {
return nil, err
}
@@ -687,6 +680,14 @@ func NewPostgresqlStore(ctx context.Context, dsn string, metrics telemetry.AppMe
return NewSqlStore(ctx, db, PostgresStoreEngine, metrics)
}
+func getGormConfig() *gorm.Config {
+ return &gorm.Config{
+ Logger: logger.Default.LogMode(logger.Silent),
+ CreateBatchSize: 400,
+ PrepareStmt: true,
+ }
+}
+
// newPostgresStore initializes a new Postgres store.
func newPostgresStore(ctx context.Context, metrics telemetry.AppMetrics) (Store, error) {
dsn, ok := os.LookupEnv(postgresDsnEnv)
diff --git a/management/server/sql_store_test.go b/management/server/sql_store_test.go
index e0e893052d7..e3ba00b5605 100644
--- a/management/server/sql_store_test.go
+++ b/management/server/sql_store_test.go
@@ -41,11 +41,22 @@ func TestSqlite_NewStore(t *testing.T) {
}
func TestSqlite_SaveAccount_Large(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip("The SQLite store is not properly supported by Windows yet")
- }
+ if runtime.GOOS != "linux" && os.Getenv("CI") == "true" || runtime.GOOS == "windows" {
+ t.Skip("skip large test on non-linux OS due to environment restrictions")
+ }
+ t.Run("SQLite", func(t *testing.T) {
+ store := newSqliteStore(t)
+ runLargeTest(t, store)
+ })
+ // create store outside to have a better time counter for the test
+ store := newPostgresqlStore(t)
+ t.Run("PostgreSQL", func(t *testing.T) {
+ runLargeTest(t, store)
+ })
+}
- store := newSqliteStore(t)
+func runLargeTest(t *testing.T, store Store) {
+ t.Helper()
account := newAccountWithId(context.Background(), "account_id", "testuser", "")
groupALL, err := account.GetGroupAll()
@@ -54,7 +65,7 @@ func TestSqlite_SaveAccount_Large(t *testing.T) {
}
setupKey := GenerateDefaultSetupKey()
account.SetupKeys[setupKey.Key] = setupKey
- const numPerAccount = 2000
+ const numPerAccount = 6000
for n := 0; n < numPerAccount; n++ {
netIP := randomIPv4()
peerID := fmt.Sprintf("%s-peer-%d", account.Id, n)
From 47752e15736d4e4dc5e08eadd828c8773ebce899 Mon Sep 17 00:00:00 2001
From: pascal-fischer <32096965+pascal-fischer@users.noreply.github.com>
Date: Mon, 15 Jul 2024 10:40:57 +0200
Subject: [PATCH 08/77] Support DNS routes on iOS (#2254)
---
client/internal/routemanager/manager.go | 20 +++---
.../routemanager/{ => notifier}/notifier.go | 47 +++++++++++---
.../routemanager/systemops/systemops.go | 13 +++-
...stemops_mobile.go => systemops_android.go} | 2 +-
.../systemops/systemops_bsd_test.go | 2 +-
.../systemops/systemops_generic_test.go | 6 +-
.../routemanager/systemops/systemops_ios.go | 64 +++++++++++++++++++
client/ios/NetBirdSDK/client.go | 28 +++++++-
client/ios/NetBirdSDK/routes.go | 29 +++++++++
9 files changed, 185 insertions(+), 26 deletions(-)
rename client/internal/routemanager/{ => notifier}/notifier.go (67%)
rename client/internal/routemanager/systemops/{systemops_mobile.go => systemops_android.go} (96%)
create mode 100644 client/internal/routemanager/systemops/systemops_ios.go
diff --git a/client/internal/routemanager/manager.go b/client/internal/routemanager/manager.go
index 0673ea6c3bd..0b10dbe33ae 100644
--- a/client/internal/routemanager/manager.go
+++ b/client/internal/routemanager/manager.go
@@ -16,6 +16,7 @@ import (
firewall "github.com/netbirdio/netbird/client/firewall/manager"
"github.com/netbirdio/netbird/client/internal/listener"
"github.com/netbirdio/netbird/client/internal/peer"
+ "github.com/netbirdio/netbird/client/internal/routemanager/notifier"
"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
"github.com/netbirdio/netbird/client/internal/routemanager/vars"
@@ -50,7 +51,7 @@ type DefaultManager struct {
statusRecorder *peer.Status
wgInterface *iface.WGIface
pubKey string
- notifier *notifier
+ notifier *notifier.Notifier
routeRefCounter *refcounter.RouteRefCounter
allowedIPsRefCounter *refcounter.AllowedIPsRefCounter
dnsRouteInterval time.Duration
@@ -65,7 +66,8 @@ func NewManager(
initialRoutes []*route.Route,
) *DefaultManager {
mCTX, cancel := context.WithCancel(ctx)
- sysOps := systemops.NewSysOps(wgInterface)
+ notifier := notifier.NewNotifier()
+ sysOps := systemops.NewSysOps(wgInterface, notifier)
dm := &DefaultManager{
ctx: mCTX,
@@ -77,7 +79,7 @@ func NewManager(
statusRecorder: statusRecorder,
wgInterface: wgInterface,
pubKey: pubKey,
- notifier: newNotifier(),
+ notifier: notifier,
}
dm.routeRefCounter = refcounter.New(
@@ -107,7 +109,7 @@ func NewManager(
if runtime.GOOS == "android" {
cr := dm.clientRoutes(initialRoutes)
- dm.notifier.setInitialClientRoutes(cr)
+ dm.notifier.SetInitialClientRoutes(cr)
}
return dm
}
@@ -186,7 +188,7 @@ func (m *DefaultManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Ro
filteredClientRoutes := m.routeSelector.FilterSelected(newClientRoutesIDMap)
m.updateClientNetworks(updateSerial, filteredClientRoutes)
- m.notifier.onNewRoutes(filteredClientRoutes)
+ m.notifier.OnNewRoutes(filteredClientRoutes)
if m.serverRouter != nil {
err := m.serverRouter.updateRoutes(newServerRoutesMap)
@@ -199,14 +201,14 @@ func (m *DefaultManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Ro
}
}
-// SetRouteChangeListener set RouteListener for route change notifier
+// SetRouteChangeListener set RouteListener for route change Notifier
func (m *DefaultManager) SetRouteChangeListener(listener listener.NetworkChangeListener) {
- m.notifier.setListener(listener)
+ m.notifier.SetListener(listener)
}
// InitialRouteRange return the list of initial routes. It used by mobile systems
func (m *DefaultManager) InitialRouteRange() []string {
- return m.notifier.getInitialRouteRanges()
+ return m.notifier.GetInitialRouteRanges()
}
// GetRouteSelector returns the route selector
@@ -226,7 +228,7 @@ func (m *DefaultManager) TriggerSelection(networks route.HAMap) {
networks = m.routeSelector.FilterSelected(networks)
- m.notifier.onNewRoutes(networks)
+ m.notifier.OnNewRoutes(networks)
m.stopObsoleteClients(networks)
diff --git a/client/internal/routemanager/notifier.go b/client/internal/routemanager/notifier/notifier.go
similarity index 67%
rename from client/internal/routemanager/notifier.go
rename to client/internal/routemanager/notifier/notifier.go
index b606c79dac3..ebdd60323eb 100644
--- a/client/internal/routemanager/notifier.go
+++ b/client/internal/routemanager/notifier/notifier.go
@@ -1,6 +1,7 @@
-package routemanager
+package notifier
import (
+ "net/netip"
"runtime"
"sort"
"strings"
@@ -10,7 +11,7 @@ import (
"github.com/netbirdio/netbird/route"
)
-type notifier struct {
+type Notifier struct {
initialRouteRanges []string
routeRanges []string
@@ -18,17 +19,17 @@ type notifier struct {
listenerMux sync.Mutex
}
-func newNotifier() *notifier {
- return ¬ifier{}
+func NewNotifier() *Notifier {
+ return &Notifier{}
}
-func (n *notifier) setListener(listener listener.NetworkChangeListener) {
+func (n *Notifier) SetListener(listener listener.NetworkChangeListener) {
n.listenerMux.Lock()
defer n.listenerMux.Unlock()
n.listener = listener
}
-func (n *notifier) setInitialClientRoutes(clientRoutes []*route.Route) {
+func (n *Notifier) SetInitialClientRoutes(clientRoutes []*route.Route) {
nets := make([]string, 0)
for _, r := range clientRoutes {
nets = append(nets, r.Network.String())
@@ -37,7 +38,10 @@ func (n *notifier) setInitialClientRoutes(clientRoutes []*route.Route) {
n.initialRouteRanges = nets
}
-func (n *notifier) onNewRoutes(idMap route.HAMap) {
+func (n *Notifier) OnNewRoutes(idMap route.HAMap) {
+ if runtime.GOOS != "android" {
+ return
+ }
newNets := make([]string, 0)
for _, routes := range idMap {
for _, r := range routes {
@@ -62,7 +66,30 @@ func (n *notifier) onNewRoutes(idMap route.HAMap) {
n.notify()
}
-func (n *notifier) notify() {
+func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) {
+ newNets := make([]string, 0)
+ for _, prefix := range prefixes {
+ newNets = append(newNets, prefix.String())
+ }
+
+ sort.Strings(newNets)
+ switch runtime.GOOS {
+ case "android":
+ if !n.hasDiff(n.initialRouteRanges, newNets) {
+ return
+ }
+ default:
+ if !n.hasDiff(n.routeRanges, newNets) {
+ return
+ }
+ }
+
+ n.routeRanges = newNets
+
+ n.notify()
+}
+
+func (n *Notifier) notify() {
n.listenerMux.Lock()
defer n.listenerMux.Unlock()
if n.listener == nil {
@@ -74,7 +101,7 @@ func (n *notifier) notify() {
}(n.listener)
}
-func (n *notifier) hasDiff(a []string, b []string) bool {
+func (n *Notifier) hasDiff(a []string, b []string) bool {
if len(a) != len(b) {
return true
}
@@ -86,7 +113,7 @@ func (n *notifier) hasDiff(a []string, b []string) bool {
return false
}
-func (n *notifier) getInitialRouteRanges() []string {
+func (n *Notifier) GetInitialRouteRanges() []string {
return addIPv6RangeIfNeeded(n.initialRouteRanges)
}
diff --git a/client/internal/routemanager/systemops/systemops.go b/client/internal/routemanager/systemops/systemops.go
index 9ee51538b5a..cddd7e7e242 100644
--- a/client/internal/routemanager/systemops/systemops.go
+++ b/client/internal/routemanager/systemops/systemops.go
@@ -3,7 +3,9 @@ package systemops
import (
"net"
"net/netip"
+ "sync"
+ "github.com/netbirdio/netbird/client/internal/routemanager/notifier"
"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
"github.com/netbirdio/netbird/iface"
)
@@ -18,10 +20,19 @@ type ExclusionCounter = refcounter.Counter[any, Nexthop]
type SysOps struct {
refCounter *ExclusionCounter
wgInterface *iface.WGIface
+ // prefixes is tracking all the current added prefixes im memory
+ // (this is used in iOS as all route updates require a full table update)
+ //nolint
+ prefixes map[netip.Prefix]struct{}
+ //nolint
+ mu sync.Mutex
+ // notifier is used to notify the system of route changes (also used on mobile)
+ notifier *notifier.Notifier
}
-func NewSysOps(wgInterface *iface.WGIface) *SysOps {
+func NewSysOps(wgInterface *iface.WGIface, notifier *notifier.Notifier) *SysOps {
return &SysOps{
wgInterface: wgInterface,
+ notifier: notifier,
}
}
diff --git a/client/internal/routemanager/systemops/systemops_mobile.go b/client/internal/routemanager/systemops/systemops_android.go
similarity index 96%
rename from client/internal/routemanager/systemops/systemops_mobile.go
rename to client/internal/routemanager/systemops/systemops_android.go
index 43815c65705..5e97a4a5f53 100644
--- a/client/internal/routemanager/systemops/systemops_mobile.go
+++ b/client/internal/routemanager/systemops/systemops_android.go
@@ -1,4 +1,4 @@
-//go:build ios || android
+//go:build android
package systemops
diff --git a/client/internal/routemanager/systemops/systemops_bsd_test.go b/client/internal/routemanager/systemops/systemops_bsd_test.go
index ce9a9082a11..84b84483e49 100644
--- a/client/internal/routemanager/systemops/systemops_bsd_test.go
+++ b/client/internal/routemanager/systemops/systemops_bsd_test.go
@@ -36,7 +36,7 @@ func TestConcurrentRoutes(t *testing.T) {
baseIP := netip.MustParseAddr("192.0.2.0")
intf := &net.Interface{Name: "lo0"}
- r := NewSysOps(nil)
+ r := NewSysOps(nil, nil)
var wg sync.WaitGroup
for i := 0; i < 1024; i++ {
diff --git a/client/internal/routemanager/systemops/systemops_generic_test.go b/client/internal/routemanager/systemops/systemops_generic_test.go
index 292166582be..94965c119b9 100644
--- a/client/internal/routemanager/systemops/systemops_generic_test.go
+++ b/client/internal/routemanager/systemops/systemops_generic_test.go
@@ -68,7 +68,7 @@ func TestAddRemoveRoutes(t *testing.T) {
err = wgInterface.Create()
require.NoError(t, err, "should create testing wireguard interface")
- r := NewSysOps(wgInterface)
+ r := NewSysOps(wgInterface, nil)
_, _, err = r.SetupRouting(nil)
require.NoError(t, err)
@@ -224,7 +224,7 @@ func TestAddExistAndRemoveRoute(t *testing.T) {
require.NoError(t, err, "InterfaceByName should not return err")
intf := &net.Interface{Index: index.Index, Name: wgInterface.Name()}
- r := NewSysOps(wgInterface)
+ r := NewSysOps(wgInterface, nil)
// Prepare the environment
if testCase.preExistingPrefix.IsValid() {
@@ -379,7 +379,7 @@ func setupTestEnv(t *testing.T) {
assert.NoError(t, wgInterface.Close())
})
- r := NewSysOps(wgInterface)
+ r := NewSysOps(wgInterface, nil)
_, _, err := r.SetupRouting(nil)
require.NoError(t, err, "setupRouting should not return err")
t.Cleanup(func() {
diff --git a/client/internal/routemanager/systemops/systemops_ios.go b/client/internal/routemanager/systemops/systemops_ios.go
new file mode 100644
index 00000000000..7cfb2b29895
--- /dev/null
+++ b/client/internal/routemanager/systemops/systemops_ios.go
@@ -0,0 +1,64 @@
+//go:build ios
+
+package systemops
+
+import (
+ "net"
+ "net/netip"
+ "runtime"
+
+ log "github.com/sirupsen/logrus"
+
+ nbnet "github.com/netbirdio/netbird/util/net"
+)
+
+func (r *SysOps) SetupRouting([]net.IP) (nbnet.AddHookFunc, nbnet.RemoveHookFunc, error) {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ r.prefixes = make(map[netip.Prefix]struct{})
+ return nil, nil, nil
+}
+
+func (r *SysOps) CleanupRouting() error {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ r.prefixes = make(map[netip.Prefix]struct{})
+ r.notify()
+ return nil
+}
+
+func (r *SysOps) AddVPNRoute(prefix netip.Prefix, _ *net.Interface) error {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ r.prefixes[prefix] = struct{}{}
+ r.notify()
+ return nil
+}
+
+func (r *SysOps) RemoveVPNRoute(prefix netip.Prefix, _ *net.Interface) error {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ delete(r.prefixes, prefix)
+ r.notify()
+ return nil
+}
+
+func EnableIPForwarding() error {
+ log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS)
+ return nil
+}
+
+func IsAddrRouted(netip.Addr, []netip.Prefix) (bool, netip.Prefix) {
+ return false, netip.Prefix{}
+}
+
+func (r *SysOps) notify() {
+ prefixes := make([]netip.Prefix, 0, len(r.prefixes))
+ for prefix := range r.prefixes {
+ prefixes = append(prefixes, prefix)
+ }
+ r.notifier.OnNewPrefixes(prefixes)
+}
diff --git a/client/ios/NetBirdSDK/client.go b/client/ios/NetBirdSDK/client.go
index d96f035dfab..d80072c78ad 100644
--- a/client/ios/NetBirdSDK/client.go
+++ b/client/ios/NetBirdSDK/client.go
@@ -19,6 +19,7 @@ import (
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/system"
"github.com/netbirdio/netbird/formatter"
+ "github.com/netbirdio/netbird/management/domain"
"github.com/netbirdio/netbird/route"
)
@@ -47,6 +48,7 @@ type CustomLogger interface {
type selectRoute struct {
NetID string
Network netip.Prefix
+ Domains domain.List
Selected bool
}
@@ -279,6 +281,7 @@ func (c *Client) GetRoutesSelectionDetails() (*RoutesSelectionDetails, error) {
route := &selectRoute{
NetID: string(id),
Network: rt[0].Network,
+ Domains: rt[0].Domains,
Selected: routeSelector.IsSelected(id),
}
routes = append(routes, route)
@@ -299,17 +302,40 @@ func (c *Client) GetRoutesSelectionDetails() (*RoutesSelectionDetails, error) {
return iPrefix < jPrefix
})
+ resolvedDomains := c.recorder.GetResolvedDomainsStates()
+
+ return prepareRouteSelectionDetails(routes, resolvedDomains), nil
+
+}
+
+func prepareRouteSelectionDetails(routes []*selectRoute, resolvedDomains map[domain.Domain][]netip.Prefix) *RoutesSelectionDetails {
var routeSelection []RoutesSelectionInfo
for _, r := range routes {
+ domainList := make([]DomainInfo, 0)
+ for _, d := range r.Domains {
+ domainResp := DomainInfo{
+ Domain: d.SafeString(),
+ }
+ if prefixes, exists := resolvedDomains[d]; exists {
+ var ipStrings []string
+ for _, prefix := range prefixes {
+ ipStrings = append(ipStrings, prefix.Addr().String())
+ }
+ domainResp.ResolvedIPs = strings.Join(ipStrings, ", ")
+ }
+ domainList = append(domainList, domainResp)
+ }
+ domainDetails := DomainDetails{items: domainList}
routeSelection = append(routeSelection, RoutesSelectionInfo{
ID: r.NetID,
Network: r.Network.String(),
+ Domains: &domainDetails,
Selected: r.Selected,
})
}
routeSelectionDetails := RoutesSelectionDetails{items: routeSelection}
- return &routeSelectionDetails, nil
+ return &routeSelectionDetails
}
func (c *Client) SelectRoute(id string) error {
diff --git a/client/ios/NetBirdSDK/routes.go b/client/ios/NetBirdSDK/routes.go
index 63536255bb6..30d0d0d0a25 100644
--- a/client/ios/NetBirdSDK/routes.go
+++ b/client/ios/NetBirdSDK/routes.go
@@ -16,9 +16,25 @@ type RoutesSelectionDetails struct {
type RoutesSelectionInfo struct {
ID string
Network string
+ Domains *DomainDetails
Selected bool
}
+type DomainCollection interface {
+ Add(s DomainInfo) DomainCollection
+ Get(i int) *DomainInfo
+ Size() int
+}
+
+type DomainDetails struct {
+ items []DomainInfo
+}
+
+type DomainInfo struct {
+ Domain string
+ ResolvedIPs string
+}
+
// Add new PeerInfo to the collection
func (array RoutesSelectionDetails) Add(s RoutesSelectionInfo) RoutesSelectionDetails {
array.items = append(array.items, s)
@@ -34,3 +50,16 @@ func (array RoutesSelectionDetails) Get(i int) *RoutesSelectionInfo {
func (array RoutesSelectionDetails) Size() int {
return len(array.items)
}
+
+func (array DomainDetails) Add(s DomainInfo) DomainCollection {
+ array.items = append(array.items, s)
+ return array
+}
+
+func (array DomainDetails) Get(i int) *DomainInfo {
+ return &array.items[i]
+}
+
+func (array DomainDetails) Size() int {
+ return len(array.items)
+}
From d5ba2ef6ec227e59e8d943e6ef3d028a92d3236e Mon Sep 17 00:00:00 2001
From: Edouard Vanbelle <15628033+EdouardVanbelle@users.noreply.github.com>
Date: Mon, 15 Jul 2024 14:43:50 +0200
Subject: [PATCH 09/77] fix 2260: fallback serial to Board (#2263)
---
client/system/info_linux.go | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/client/system/info_linux.go b/client/system/info_linux.go
index d85a6faec2c..db58d913fdb 100644
--- a/client/system/info_linux.go
+++ b/client/system/info_linux.go
@@ -8,6 +8,7 @@ import (
"context"
"os"
"os/exec"
+ "regexp"
"runtime"
"strings"
"time"
@@ -89,9 +90,17 @@ func _getInfo() string {
func sysInfo() (serialNumber string, productName string, manufacturer string) {
var si sysinfo.SysInfo
si.GetSysInfo()
+ isascii := regexp.MustCompile("^[[:ascii:]]+$")
serial := si.Chassis.Serial
if (serial == "Default string" || serial == "") && si.Product.Serial != "" {
serial = si.Product.Serial
}
- return serial, si.Product.Name, si.Product.Vendor
+ if (!isascii.MatchString(serial)) && si.Board.Serial != "" {
+ serial = si.Board.Serial
+ }
+ name := si.Product.Name
+ if (!isascii.MatchString(name)) && si.Board.Name != "" {
+ name = si.Board.Name
+ }
+ return serial, name, si.Product.Vendor
}
From bc09348f5a59d59622eb246a282b9fbff8214755 Mon Sep 17 00:00:00 2001
From: Zoltan Papp
Date: Mon, 15 Jul 2024 14:45:18 +0200
Subject: [PATCH 10/77] Add logging option for wg device (#2271)
---
iface/tun_android.go | 2 +-
iface/tun_darwin.go | 2 +-
iface/tun_ios.go | 2 +-
iface/tun_netstack.go | 2 +-
iface/tun_usp_unix.go | 2 +-
iface/tun_windows.go | 3 ++-
iface/wg_log.go | 15 +++++++++++++++
7 files changed, 22 insertions(+), 6 deletions(-)
create mode 100644 iface/wg_log.go
diff --git a/iface/tun_android.go b/iface/tun_android.go
index dc6abea36b7..50499309413 100644
--- a/iface/tun_android.go
+++ b/iface/tun_android.go
@@ -64,7 +64,7 @@ func (t *wgTunDevice) Create(routes []string, dns string, searchDomains []string
t.wrapper = newDeviceWrapper(tunDevice)
log.Debugf("attaching to interface %v", name)
- t.device = device.NewDevice(t.wrapper, t.iceBind, device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
+ t.device = device.NewDevice(t.wrapper, t.iceBind, device.NewLogger(wgLogLevel(), "[wiretrustee] "))
// without this property mobile devices can discover remote endpoints if the configured one was wrong.
// this helps with support for the older NetBird clients that had a hardcoded direct mode
// t.device.DisableSomeRoamingForBrokenMobileSemantics()
diff --git a/iface/tun_darwin.go b/iface/tun_darwin.go
index 7d684f52e96..364e5dfadce 100644
--- a/iface/tun_darwin.go
+++ b/iface/tun_darwin.go
@@ -49,7 +49,7 @@ func (t *tunDevice) Create() (wgConfigurer, error) {
t.device = device.NewDevice(
t.wrapper,
t.iceBind,
- device.NewLogger(device.LogLevelSilent, "[netbird] "),
+ device.NewLogger(wgLogLevel(), "[netbird] "),
)
err = t.assignAddr()
diff --git a/iface/tun_ios.go b/iface/tun_ios.go
index 83e26e08d6c..6d53cc33366 100644
--- a/iface/tun_ios.go
+++ b/iface/tun_ios.go
@@ -64,7 +64,7 @@ func (t *tunDevice) Create() (wgConfigurer, error) {
t.wrapper = newDeviceWrapper(tunDevice)
log.Debug("Attaching to interface")
- t.device = device.NewDevice(t.wrapper, t.iceBind, device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
+ t.device = device.NewDevice(t.wrapper, t.iceBind, device.NewLogger(wgLogLevel(), "[wiretrustee] "))
// without this property mobile devices can discover remote endpoints if the configured one was wrong.
// this helps with support for the older NetBird clients that had a hardcoded direct mode
// t.device.DisableSomeRoamingForBrokenMobileSemantics()
diff --git a/iface/tun_netstack.go b/iface/tun_netstack.go
index beb3acc3fef..df2f75c4539 100644
--- a/iface/tun_netstack.go
+++ b/iface/tun_netstack.go
@@ -54,7 +54,7 @@ func (t *tunNetstackDevice) Create() (wgConfigurer, error) {
t.device = device.NewDevice(
t.wrapper,
t.iceBind,
- device.NewLogger(device.LogLevelSilent, "[netbird] "),
+ device.NewLogger(wgLogLevel(), "[netbird] "),
)
t.configurer = newWGUSPConfigurer(t.device, t.name)
diff --git a/iface/tun_usp_unix.go b/iface/tun_usp_unix.go
index b18794b2579..814c9ca89fa 100644
--- a/iface/tun_usp_unix.go
+++ b/iface/tun_usp_unix.go
@@ -57,7 +57,7 @@ func (t *tunUSPDevice) Create() (wgConfigurer, error) {
t.device = device.NewDevice(
t.wrapper,
t.iceBind,
- device.NewLogger(device.LogLevelSilent, "[netbird] "),
+ device.NewLogger(wgLogLevel(), "[netbird] "),
)
err = t.assignAddr()
diff --git a/iface/tun_windows.go b/iface/tun_windows.go
index 5c77f1d166b..0d658059fd5 100644
--- a/iface/tun_windows.go
+++ b/iface/tun_windows.go
@@ -41,6 +41,7 @@ func newTunDevice(name string, address WGAddress, port int, key string, mtu int,
}
func (t *tunDevice) Create() (wgConfigurer, error) {
+ log.Info("create tun interface")
tunDevice, err := tun.CreateTUN(t.name, t.mtu)
if err != nil {
return nil, err
@@ -52,7 +53,7 @@ func (t *tunDevice) Create() (wgConfigurer, error) {
t.device = device.NewDevice(
t.wrapper,
t.iceBind,
- device.NewLogger(device.LogLevelSilent, "[netbird] "),
+ device.NewLogger(wgLogLevel(), "[netbird] "),
)
luid := winipcfg.LUID(t.nativeTunDevice.LUID())
diff --git a/iface/wg_log.go b/iface/wg_log.go
new file mode 100644
index 00000000000..b44f6fc0b28
--- /dev/null
+++ b/iface/wg_log.go
@@ -0,0 +1,15 @@
+package iface
+
+import (
+ "os"
+
+ "golang.zx2c4.com/wireguard/device"
+)
+
+func wgLogLevel() int {
+ if os.Getenv("NB_WG_DEBUG") == "true" {
+ return device.LogLevelVerbose
+ } else {
+ return device.LogLevelSilent
+ }
+}
From 2577100096f63693eefc0589ca08b9f351c4a1ae Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Mon, 15 Jul 2024 14:53:52 +0200
Subject: [PATCH 11/77] Limit GUI process execution to one per UID (#2267)
replaces PID with checking process name and path and UID checks
---
client/ui/client_ui.go | 43 ++++++++++++++++++---------------
client/ui/process.go | 37 ++++++++++++++++++++++++++++
client/ui/process_nonwindows.go | 26 ++++++++++++++++++++
client/ui/process_windows.go | 24 ++++++++++++++++++
4 files changed, 111 insertions(+), 19 deletions(-)
create mode 100644 client/ui/process.go
create mode 100644 client/ui/process_nonwindows.go
create mode 100644 client/ui/process_windows.go
diff --git a/client/ui/client_ui.go b/client/ui/client_ui.go
index 67f3ef07bfd..58004dd4a5d 100644
--- a/client/ui/client_ui.go
+++ b/client/ui/client_ui.go
@@ -15,7 +15,6 @@ import (
"strconv"
"strings"
"sync"
- "syscall"
"time"
"unicode"
@@ -34,6 +33,7 @@ import (
"github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/proto"
"github.com/netbirdio/netbird/client/system"
+ "github.com/netbirdio/netbird/util"
"github.com/netbirdio/netbird/version"
)
@@ -62,8 +62,25 @@ func main() {
var errorMSG string
flag.StringVar(&errorMSG, "error-msg", "", "displays a error message window")
+ tmpDir := "/tmp"
+ if runtime.GOOS == "windows" {
+ tmpDir = os.TempDir()
+ }
+
+ var saveLogsInFile bool
+ flag.BoolVar(&saveLogsInFile, "use-log-file", false, fmt.Sprintf("save logs in a file: %s/netbird-ui-PID.log", tmpDir))
+
flag.Parse()
+ if saveLogsInFile {
+ logFile := path.Join(tmpDir, fmt.Sprintf("netbird-ui-%d.log", os.Getpid()))
+ err := util.InitLog("trace", logFile)
+ if err != nil {
+ log.Errorf("error while initializing log: %v", err)
+ return
+ }
+ }
+
a := app.NewWithID("NetBird")
a.SetIcon(fyne.NewStaticResource("netbird", iconDisconnectedPNG))
@@ -76,8 +93,12 @@ func main() {
if showSettings || showRoutes {
a.Run()
} else {
- if err := checkPIDFile(); err != nil {
- log.Errorf("check PID file: %v", err)
+ running, err := isAnotherProcessRunning()
+ if err != nil {
+ log.Errorf("error while checking process: %v", err)
+ }
+ if running {
+ log.Warn("another process is running")
return
}
client.setDefaultFonts()
@@ -861,19 +882,3 @@ func openURL(url string) error {
}
return err
}
-
-// checkPIDFile exists and return error, or write new.
-func checkPIDFile() error {
- pidFile := path.Join(os.TempDir(), "wiretrustee-ui.pid")
- if piddata, err := os.ReadFile(pidFile); err == nil {
- if pid, err := strconv.Atoi(string(piddata)); err == nil {
- if process, err := os.FindProcess(pid); err == nil {
- if err := process.Signal(syscall.Signal(0)); err == nil {
- return fmt.Errorf("process already exists: %d", pid)
- }
- }
- }
- }
-
- return os.WriteFile(pidFile, []byte(fmt.Sprintf("%d", os.Getpid())), 0o664) //nolint:gosec
-}
diff --git a/client/ui/process.go b/client/ui/process.go
new file mode 100644
index 00000000000..bcb3dd879d4
--- /dev/null
+++ b/client/ui/process.go
@@ -0,0 +1,37 @@
+package main
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/shirou/gopsutil/v3/process"
+)
+
+func isAnotherProcessRunning() (bool, error) {
+ processes, err := process.Processes()
+ if err != nil {
+ return false, err
+ }
+
+ pid := os.Getpid()
+ processName := strings.ToLower(filepath.Base(os.Args[0]))
+
+ for _, p := range processes {
+ if int(p.Pid) == pid {
+ continue
+ }
+
+ runningProcessPath, err := p.Exe()
+ // most errors are related to short-lived processes
+ if err != nil {
+ continue
+ }
+
+ if strings.Contains(strings.ToLower(runningProcessPath), processName) && isProcessOwnedByCurrentUser(p) {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
diff --git a/client/ui/process_nonwindows.go b/client/ui/process_nonwindows.go
new file mode 100644
index 00000000000..0d17be2be34
--- /dev/null
+++ b/client/ui/process_nonwindows.go
@@ -0,0 +1,26 @@
+//go:build !windows
+
+package main
+
+import (
+ "os"
+
+ "github.com/shirou/gopsutil/v3/process"
+ log "github.com/sirupsen/logrus"
+)
+
+func isProcessOwnedByCurrentUser(p *process.Process) bool {
+ currentUserID := os.Getuid()
+ uids, err := p.Uids()
+ if err != nil {
+ log.Errorf("get process uids: %v", err)
+ return false
+ }
+ for _, id := range uids {
+ log.Debugf("checking process uid: %d", id)
+ if int(id) == currentUserID {
+ return true
+ }
+ }
+ return false
+}
diff --git a/client/ui/process_windows.go b/client/ui/process_windows.go
new file mode 100644
index 00000000000..b15b0ed2408
--- /dev/null
+++ b/client/ui/process_windows.go
@@ -0,0 +1,24 @@
+package main
+
+import (
+ "os/user"
+
+ "github.com/shirou/gopsutil/v3/process"
+ log "github.com/sirupsen/logrus"
+)
+
+func isProcessOwnedByCurrentUser(p *process.Process) bool {
+ processUsername, err := p.Username()
+ if err != nil {
+ log.Errorf("get process username error: %v", err)
+ return false
+ }
+
+ currUser, err := user.Current()
+ if err != nil {
+ log.Errorf("get current user error: %v", err)
+ return false
+ }
+
+ return processUsername == currUser.Username
+}
From 1537b0f5e7be8ee59faace8786fe97a7d6255ab4 Mon Sep 17 00:00:00 2001
From: Bethuel Mmbaga
Date: Mon, 15 Jul 2024 17:04:06 +0300
Subject: [PATCH 12/77] Add batch save/update for groups and users (#2245)
* Add functionality to update multiple users
* Remove SaveUsers from DefaultAccountManager
* Add SaveGroups method to AccountManager interface
* Refactoring
* Add SaveUsers and SaveGroups methods to store interface
* Refactor method SaveAccount to SaveUsers and SaveGroups
The method SaveAccount in user.go and group.go files was split into two separate methods. Now, user-specific data is handled by SaveUsers and group-specific data is handled by SaveGroups method. This provides a cleaner and more efficient way to save user and group data.
* Add account ID to user and group in SqlStore
* Refactor SaveUsers and SaveGroups in store
* Remove unnecessary ID assignment in SaveUsers and SaveGroups
---
management/server/account.go | 2 +
management/server/file_store.go | 8 +
management/server/group.go | 106 ++++---
management/server/mock_server/account_mock.go | 18 ++
management/server/sql_store.go | 28 ++
management/server/store.go | 3 +
management/server/user.go | 290 +++++++++++-------
7 files changed, 310 insertions(+), 145 deletions(-)
diff --git a/management/server/account.go b/management/server/account.go
index 27c21e4028f..558de6fbb76 100644
--- a/management/server/account.go
+++ b/management/server/account.go
@@ -69,6 +69,7 @@ type AccountManager interface {
ListSetupKeys(ctx context.Context, accountID, userID string) ([]*SetupKey, error)
SaveUser(ctx context.Context, accountID, initiatorUserID string, update *User) (*UserInfo, error)
SaveOrAddUser(ctx context.Context, accountID, initiatorUserID string, update *User, addIfNotExists bool) (*UserInfo, error)
+ SaveOrAddUsers(ctx context.Context, accountID, initiatorUserID string, updates []*User, addIfNotExists bool) ([]*UserInfo, error)
GetSetupKey(ctx context.Context, accountID, userID, keyID string) (*SetupKey, error)
GetAccountByUserOrAccountID(ctx context.Context, userID, accountID, domain string) (*Account, error)
GetAccountFromToken(ctx context.Context, claims jwtclaims.AuthorizationClaims) (*Account, *User, error)
@@ -95,6 +96,7 @@ type AccountManager interface {
GetAllGroups(ctx context.Context, accountID, userID string) ([]*nbgroup.Group, error)
GetGroupByName(ctx context.Context, groupName, accountID string) (*nbgroup.Group, error)
SaveGroup(ctx context.Context, accountID, userID string, group *nbgroup.Group) error
+ SaveGroups(ctx context.Context, accountID, userID string, newGroups []*nbgroup.Group) error
DeleteGroup(ctx context.Context, accountId, userId, groupID string) error
ListGroups(ctx context.Context, accountId string) ([]*nbgroup.Group, error)
GroupAddPeer(ctx context.Context, accountId, groupID, peerID string) error
diff --git a/management/server/file_store.go b/management/server/file_store.go
index 3fd54379736..c649602e2a2 100644
--- a/management/server/file_store.go
+++ b/management/server/file_store.go
@@ -746,3 +746,11 @@ func (s *FileStore) Close(ctx context.Context) error {
func (s *FileStore) GetStoreEngine() StoreEngine {
return FileStoreEngine
}
+
+func (s *FileStore) SaveUsers(accountID string, users map[string]*User) error {
+ return status.Errorf(status.Internal, "SaveUsers is not implemented")
+}
+
+func (s *FileStore) SaveGroups(accountID string, groups map[string]*nbgroup.Group) error {
+ return status.Errorf(status.Internal, "SaveGroups is not implemented")
+}
diff --git a/management/server/group.go b/management/server/group.go
index ea512924bfa..45c51bda237 100644
--- a/management/server/group.go
+++ b/management/server/group.go
@@ -112,61 +112,85 @@ func (am *DefaultAccountManager) GetGroupByName(ctx context.Context, groupName,
func (am *DefaultAccountManager) SaveGroup(ctx context.Context, accountID, userID string, newGroup *nbgroup.Group) error {
unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
defer unlock()
+ return am.SaveGroups(ctx, accountID, userID, []*nbgroup.Group{newGroup})
+}
+// SaveGroups adds new groups to the account.
+// Note: This function does not acquire the global lock.
+// It is the caller's responsibility to ensure proper locking is in place before invoking this method.
+func (am *DefaultAccountManager) SaveGroups(ctx context.Context, accountID, userID string, newGroups []*nbgroup.Group) error {
account, err := am.Store.GetAccount(ctx, accountID)
if err != nil {
return err
}
- if newGroup.ID == "" && newGroup.Issued != nbgroup.GroupIssuedAPI {
- return status.Errorf(status.InvalidArgument, "%s group without ID set", newGroup.Issued)
- }
+ var eventsToStore []func()
- if newGroup.ID == "" && newGroup.Issued == nbgroup.GroupIssuedAPI {
+ for _, newGroup := range newGroups {
+ if newGroup.ID == "" && newGroup.Issued != nbgroup.GroupIssuedAPI {
+ return status.Errorf(status.InvalidArgument, "%s group without ID set", newGroup.Issued)
+ }
- existingGroup, err := account.FindGroupByName(newGroup.Name)
- if err != nil {
- s, ok := status.FromError(err)
- if !ok || s.ErrorType != status.NotFound {
- return err
+ if newGroup.ID == "" && newGroup.Issued == nbgroup.GroupIssuedAPI {
+ existingGroup, err := account.FindGroupByName(newGroup.Name)
+ if err != nil {
+ s, ok := status.FromError(err)
+ if !ok || s.ErrorType != status.NotFound {
+ return err
+ }
}
+
+ // Avoid duplicate groups only for the API issued groups.
+ // Integration or JWT groups can be duplicated as they are coming from the IdP that we don't have control of.
+ if existingGroup != nil {
+ return status.Errorf(status.AlreadyExists, "group with name %s already exists", newGroup.Name)
+ }
+
+ newGroup.ID = xid.New().String()
}
- // avoid duplicate groups only for the API issued groups. Integration or JWT groups can be duplicated as they are
- // coming from the IdP that we don't have control of.
- if existingGroup != nil {
- return status.Errorf(status.AlreadyExists, "group with name %s already exists", newGroup.Name)
+ for _, peerID := range newGroup.Peers {
+ if account.Peers[peerID] == nil {
+ return status.Errorf(status.InvalidArgument, "peer with ID \"%s\" not found", peerID)
+ }
}
- newGroup.ID = xid.New().String()
- }
+ oldGroup := account.Groups[newGroup.ID]
+ account.Groups[newGroup.ID] = newGroup
- for _, peerID := range newGroup.Peers {
- if account.Peers[peerID] == nil {
- return status.Errorf(status.InvalidArgument, "peer with ID \"%s\" not found", peerID)
- }
+ events := am.prepareGroupEvents(ctx, userID, accountID, newGroup, oldGroup, account)
+ eventsToStore = append(eventsToStore, events...)
}
- oldGroup, exists := account.Groups[newGroup.ID]
- account.Groups[newGroup.ID] = newGroup
-
account.Network.IncSerial()
- if err = am.Store.SaveAccount(ctx, account); err != nil {
+ if err = am.Store.SaveGroups(account.Id, account.Groups); err != nil {
return err
}
am.updateAccountPeers(ctx, account)
- // the following snippet tracks the activity and stores the group events in the event store.
- // It has to happen after all the operations have been successfully performed.
+ for _, storeEvent := range eventsToStore {
+ storeEvent()
+ }
+
+ return nil
+}
+
+// prepareGroupEvents prepares a list of event functions to be stored.
+func (am *DefaultAccountManager) prepareGroupEvents(ctx context.Context, userID string, accountID string, newGroup, oldGroup *nbgroup.Group, account *Account) []func() {
+ var eventsToStore []func()
+
addedPeers := make([]string, 0)
removedPeers := make([]string, 0)
- if exists {
+
+ if oldGroup != nil {
addedPeers = difference(newGroup.Peers, oldGroup.Peers)
removedPeers = difference(oldGroup.Peers, newGroup.Peers)
} else {
addedPeers = append(addedPeers, newGroup.Peers...)
- am.StoreEvent(ctx, userID, newGroup.ID, accountID, activity.GroupCreated, newGroup.EventMeta())
+ eventsToStore = append(eventsToStore, func() {
+ am.StoreEvent(ctx, userID, newGroup.ID, accountID, activity.GroupCreated, newGroup.EventMeta())
+ })
}
for _, p := range addedPeers {
@@ -175,11 +199,14 @@ func (am *DefaultAccountManager) SaveGroup(ctx context.Context, accountID, userI
log.WithContext(ctx).Errorf("peer %s not found under account %s while saving group", p, accountID)
continue
}
- am.StoreEvent(ctx, userID, peer.ID, accountID, activity.GroupAddedToPeer,
- map[string]any{
- "group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(),
- "peer_fqdn": peer.FQDN(am.GetDNSDomain()),
- })
+ peerCopy := peer // copy to avoid closure issues
+ eventsToStore = append(eventsToStore, func() {
+ am.StoreEvent(ctx, userID, peerCopy.ID, accountID, activity.GroupAddedToPeer,
+ map[string]any{
+ "group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peerCopy.IP.String(),
+ "peer_fqdn": peerCopy.FQDN(am.GetDNSDomain()),
+ })
+ })
}
for _, p := range removedPeers {
@@ -188,14 +215,17 @@ func (am *DefaultAccountManager) SaveGroup(ctx context.Context, accountID, userI
log.WithContext(ctx).Errorf("peer %s not found under account %s while saving group", p, accountID)
continue
}
- am.StoreEvent(ctx, userID, peer.ID, accountID, activity.GroupRemovedFromPeer,
- map[string]any{
- "group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(),
- "peer_fqdn": peer.FQDN(am.GetDNSDomain()),
- })
+ peerCopy := peer // copy to avoid closure issues
+ eventsToStore = append(eventsToStore, func() {
+ am.StoreEvent(ctx, userID, peerCopy.ID, accountID, activity.GroupRemovedFromPeer,
+ map[string]any{
+ "group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peerCopy.IP.String(),
+ "peer_fqdn": peerCopy.FQDN(am.GetDNSDomain()),
+ })
+ })
}
- return nil
+ return eventsToStore
}
// difference returns the elements in `a` that aren't in `b`.
diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go
index 177088ac5a4..25bcdfcee71 100644
--- a/management/server/mock_server/account_mock.go
+++ b/management/server/mock_server/account_mock.go
@@ -40,6 +40,7 @@ type MockAccountManager struct {
GetAllGroupsFunc func(ctx context.Context, accountID, userID string) ([]*group.Group, error)
GetGroupByNameFunc func(ctx context.Context, accountID, groupName string) (*group.Group, error)
SaveGroupFunc func(ctx context.Context, accountID, userID string, group *group.Group) error
+ SaveGroupsFunc func(ctx context.Context, accountID, userID string, groups []*group.Group) error
DeleteGroupFunc func(ctx context.Context, accountID, userId, groupID string) error
ListGroupsFunc func(ctx context.Context, accountID string) ([]*group.Group, error)
GroupAddPeerFunc func(ctx context.Context, accountID, groupID, peerID string) error
@@ -64,6 +65,7 @@ type MockAccountManager struct {
ListSetupKeysFunc func(ctx context.Context, accountID, userID string) ([]*server.SetupKey, error)
SaveUserFunc func(ctx context.Context, accountID, userID string, user *server.User) (*server.UserInfo, error)
SaveOrAddUserFunc func(ctx context.Context, accountID, userID string, user *server.User, addIfNotExists bool) (*server.UserInfo, error)
+ SaveOrAddUsersFunc func(ctx context.Context, accountID, initiatorUserID string, update []*server.User, addIfNotExists bool) ([]*server.UserInfo, error)
DeleteUserFunc func(ctx context.Context, accountID string, initiatorUserID string, targetUserID string) error
CreatePATFunc func(ctx context.Context, accountID string, initiatorUserID string, targetUserId string, tokenName string, expiresIn int) (*server.PersonalAccessTokenGenerated, error)
DeletePATFunc func(ctx context.Context, accountID string, initiatorUserID string, targetUserId string, tokenID string) error
@@ -308,6 +310,14 @@ func (am *MockAccountManager) SaveGroup(ctx context.Context, accountID, userID s
return status.Errorf(codes.Unimplemented, "method SaveGroup is not implemented")
}
+// SaveGroups mock implementation of SaveGroups from server.AccountManager interface
+func (am *MockAccountManager) SaveGroups(ctx context.Context, accountID, userID string, groups []*group.Group) error {
+ if am.SaveGroupsFunc != nil {
+ return am.SaveGroupsFunc(ctx, accountID, userID, groups)
+ }
+ return status.Errorf(codes.Unimplemented, "method SaveGroups is not implemented")
+}
+
// DeleteGroup mock implementation of DeleteGroup from server.AccountManager interface
func (am *MockAccountManager) DeleteGroup(ctx context.Context, accountId, userId, groupID string) error {
if am.DeleteGroupFunc != nil {
@@ -502,6 +512,14 @@ func (am *MockAccountManager) SaveOrAddUser(ctx context.Context, accountID, user
return nil, status.Errorf(codes.Unimplemented, "method SaveOrAddUser is not implemented")
}
+// SaveOrAddUsers mocks SaveOrAddUsers of the AccountManager interface
+func (am *MockAccountManager) SaveOrAddUsers(ctx context.Context, accountID, userID string, users []*server.User, addIfNotExists bool) ([]*server.UserInfo, error) {
+ if am.SaveOrAddUsersFunc != nil {
+ return am.SaveOrAddUsersFunc(ctx, accountID, userID, users, addIfNotExists)
+ }
+ return nil, status.Errorf(codes.Unimplemented, "method SaveOrAddUsers is not implemented")
+}
+
// DeleteUser mocks DeleteUser of the AccountManager interface
func (am *MockAccountManager) DeleteUser(ctx context.Context, accountID string, initiatorUserID string, targetUserID string) error {
if am.DeleteUserFunc != nil {
diff --git a/management/server/sql_store.go b/management/server/sql_store.go
index 6bcff9f813c..41e9fde8b20 100644
--- a/management/server/sql_store.go
+++ b/management/server/sql_store.go
@@ -311,6 +311,34 @@ func (s *SqlStore) SavePeerLocation(accountID string, peerWithLocation *nbpeer.P
return nil
}
+// SaveUsers saves the given list of users to the database.
+// It updates existing users if a conflict occurs.
+func (s *SqlStore) SaveUsers(accountID string, users map[string]*User) error {
+ usersToSave := make([]User, 0, len(users))
+ for _, user := range users {
+ user.AccountID = accountID
+ for id, pat := range user.PATs {
+ pat.ID = id
+ user.PATsG = append(user.PATsG, *pat)
+ }
+ usersToSave = append(usersToSave, *user)
+ }
+ return s.db.Session(&gorm.Session{FullSaveAssociations: true}).
+ Clauses(clause.OnConflict{UpdateAll: true}).
+ Create(&usersToSave).Error
+}
+
+// SaveGroups saves the given list of groups to the database.
+// It updates existing groups if a conflict occurs.
+func (s *SqlStore) SaveGroups(accountID string, groups map[string]*nbgroup.Group) error {
+ groupsToSave := make([]nbgroup.Group, 0, len(groups))
+ for _, group := range groups {
+ group.AccountID = accountID
+ groupsToSave = append(groupsToSave, *group)
+ }
+ return s.db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&groupsToSave).Error
+}
+
// DeleteHashedPAT2TokenIDIndex is noop in SqlStore
func (s *SqlStore) DeleteHashedPAT2TokenIDIndex(hashedToken string) error {
return nil
diff --git a/management/server/store.go b/management/server/store.go
index 05a09b3eec5..3ba73e8c751 100644
--- a/management/server/store.go
+++ b/management/server/store.go
@@ -12,6 +12,7 @@ import (
"strings"
"time"
+ nbgroup "github.com/netbirdio/netbird/management/server/group"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
@@ -41,6 +42,8 @@ type Store interface {
GetUserByTokenID(ctx context.Context, tokenID string) (*User, error)
GetPostureCheckByChecksDefinition(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error)
SaveAccount(ctx context.Context, account *Account) error
+ SaveUsers(accountID string, users map[string]*User) error
+ SaveGroups(accountID string, groups map[string]*nbgroup.Group) error
DeleteHashedPAT2TokenIDIndex(hashedToken string) error
DeleteTokenID2UserIDIndex(tokenID string) error
GetInstallationID() string
diff --git a/management/server/user.go b/management/server/user.go
index 266692b8d21..65b5c787875 100644
--- a/management/server/user.go
+++ b/management/server/user.go
@@ -740,7 +740,7 @@ func (am *DefaultAccountManager) GetAllPATs(ctx context.Context, accountID strin
return pats, nil
}
-// SaveUser saves updates to the given user. If the user doesn't exit it will throw status.NotFound error.
+// SaveUser saves updates to the given user. If the user doesn't exist, it will throw status.NotFound error.
func (am *DefaultAccountManager) SaveUser(ctx context.Context, accountID, initiatorUserID string, update *User) (*UserInfo, error) {
return am.SaveOrAddUser(ctx, accountID, initiatorUserID, update, false) // false means do not create user and throw status.NotFound
}
@@ -748,165 +748,241 @@ func (am *DefaultAccountManager) SaveUser(ctx context.Context, accountID, initia
// SaveOrAddUser updates the given user. If addIfNotExists is set to true it will add user when no exist
// Only User.AutoGroups, User.Role, and User.Blocked fields are allowed to be updated for now.
func (am *DefaultAccountManager) SaveOrAddUser(ctx context.Context, accountID, initiatorUserID string, update *User, addIfNotExists bool) (*UserInfo, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
- defer unlock()
-
if update == nil {
return nil, status.Errorf(status.InvalidArgument, "provided user update is nil")
}
- account, err := am.Store.GetAccount(ctx, accountID)
- if err != nil {
- return nil, err
- }
+ unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ defer unlock()
- initiatorUser, err := account.FindUser(initiatorUserID)
+ updatedUsers, err := am.SaveOrAddUsers(ctx, accountID, initiatorUserID, []*User{update}, addIfNotExists)
if err != nil {
return nil, err
}
- if !initiatorUser.HasAdminPower() || initiatorUser.IsBlocked() {
- return nil, status.Errorf(status.PermissionDenied, "only users with admin power are authorized to perform user update operations")
+ if len(updatedUsers) == 0 {
+ return nil, status.Errorf(status.Internal, "user was not updated")
}
- oldUser := account.Users[update.Id]
- if oldUser == nil {
- if !addIfNotExists {
- return nil, status.Errorf(status.NotFound, "user to update doesn't exist")
- }
- // when addIfNotExists is set to true the newUser will use all fields from the update input
- oldUser = update
- }
+ return updatedUsers[0], nil
+}
- if initiatorUser.HasAdminPower() && initiatorUserID == update.Id && oldUser.Blocked != update.Blocked {
- return nil, status.Errorf(status.PermissionDenied, "admins can't block or unblock themselves")
+// SaveOrAddUsers updates existing users or adds new users to the account.
+// Note: This function does not acquire the global lock.
+// It is the caller's responsibility to ensure proper locking is in place before invoking this method.
+func (am *DefaultAccountManager) SaveOrAddUsers(ctx context.Context, accountID, initiatorUserID string, updates []*User, addIfNotExists bool) ([]*UserInfo, error) {
+ if len(updates) == 0 {
+ return nil, nil //nolint:nilnil
}
- if initiatorUser.HasAdminPower() && initiatorUserID == update.Id && update.Role != initiatorUser.Role {
- return nil, status.Errorf(status.PermissionDenied, "admins can't change their role")
+ account, err := am.Store.GetAccount(ctx, accountID)
+ if err != nil {
+ return nil, err
}
- if initiatorUser.Role == UserRoleAdmin && oldUser.Role == UserRoleOwner && update.Role != oldUser.Role {
- return nil, status.Errorf(status.PermissionDenied, "only owners can remove owner role from their user")
+ initiatorUser, err := account.FindUser(initiatorUserID)
+ if err != nil {
+ return nil, err
}
- if initiatorUser.Role == UserRoleAdmin && oldUser.Role == UserRoleOwner && update.IsBlocked() && !oldUser.IsBlocked() {
- return nil, status.Errorf(status.PermissionDenied, "unable to block owner user")
+ if !initiatorUser.HasAdminPower() || initiatorUser.IsBlocked() {
+ return nil, status.Errorf(status.PermissionDenied, "only users with admin power are authorized to perform user update operations")
}
- if initiatorUser.Role == UserRoleAdmin && update.Role == UserRoleOwner && update.Role != oldUser.Role {
- return nil, status.Errorf(status.PermissionDenied, "only owners can add owner role to other users")
- }
+ updatedUsers := make([]*UserInfo, 0, len(updates))
+ var (
+ expiredPeers []*nbpeer.Peer
+ eventsToStore []func()
+ )
- if oldUser.IsServiceUser && update.Role == UserRoleOwner {
- return nil, status.Errorf(status.PermissionDenied, "can't update a service user with owner role")
- }
+ for _, update := range updates {
+ if update == nil {
+ return nil, status.Errorf(status.InvalidArgument, "provided user update is nil")
+ }
- transferedOwnerRole := false
- if initiatorUser.Role == UserRoleOwner && initiatorUserID != update.Id && update.Role == UserRoleOwner {
- newInitiatorUser := initiatorUser.Copy()
- newInitiatorUser.Role = UserRoleAdmin
- account.Users[initiatorUserID] = newInitiatorUser
- transferedOwnerRole = true
- }
+ oldUser := account.Users[update.Id]
+ if oldUser == nil {
+ if !addIfNotExists {
+ return nil, status.Errorf(status.NotFound, "user to update doesn't exist: %s", update.Id)
+ }
+ // when addIfNotExists is set to true, the newUser will use all fields from the update input
+ oldUser = update
+ }
- // only auto groups, revoked status, and integration reference can be updated for now
- newUser := oldUser.Copy()
- newUser.Role = update.Role
- newUser.Blocked = update.Blocked
- // these two fields can't be set via API, only via direct call to the method
- newUser.Issued = update.Issued
- newUser.IntegrationReference = update.IntegrationReference
+ if err := validateUserUpdate(account, initiatorUser, oldUser, update); err != nil {
+ return nil, err
+ }
- for _, newGroupID := range update.AutoGroups {
- if _, ok := account.Groups[newGroupID]; !ok {
- return nil, status.Errorf(status.InvalidArgument, "provided group ID %s in the user %s update doesn't exist",
- newGroupID, update.Id)
+ // only auto groups, revoked status, and integration reference can be updated for now
+ newUser := oldUser.Copy()
+ newUser.Role = update.Role
+ newUser.Blocked = update.Blocked
+ newUser.AutoGroups = update.AutoGroups
+ // these two fields can't be set via API, only via direct call to the method
+ newUser.Issued = update.Issued
+ newUser.IntegrationReference = update.IntegrationReference
+
+ transferredOwnerRole := handleOwnerRoleTransfer(account, initiatorUser, update)
+ account.Users[newUser.Id] = newUser
+
+ if !oldUser.IsBlocked() && update.IsBlocked() {
+ // expire peers that belong to the user who's getting blocked
+ blockedPeers, err := account.FindUserPeers(update.Id)
+ if err != nil {
+ return nil, err
+ }
+ expiredPeers = append(expiredPeers, blockedPeers...)
+ }
+
+ if update.AutoGroups != nil && account.Settings.GroupsPropagationEnabled {
+ removedGroups := difference(oldUser.AutoGroups, update.AutoGroups)
+ // need force update all auto groups in any case they will not be duplicated
+ account.UserGroupsAddToPeers(oldUser.Id, update.AutoGroups...)
+ account.UserGroupsRemoveFromPeers(oldUser.Id, removedGroups...)
}
- }
- newUser.AutoGroups = update.AutoGroups
- account.Users[newUser.Id] = newUser
+ events := am.prepareUserUpdateEvents(ctx, initiatorUser.Id, oldUser, newUser, account, transferredOwnerRole)
+ eventsToStore = append(eventsToStore, events...)
- if !oldUser.IsBlocked() && update.IsBlocked() {
- // expire peers that belong to the user who's getting blocked
- blockedPeers, err := account.FindUserPeers(update.Id)
+ updatedUserInfo, err := getUserInfo(ctx, am, newUser, account)
if err != nil {
return nil, err
}
+ updatedUsers = append(updatedUsers, updatedUserInfo)
+ }
- if err := am.expireAndUpdatePeers(ctx, account, blockedPeers); err != nil {
+ if len(expiredPeers) > 0 {
+ if err := am.expireAndUpdatePeers(ctx, account, expiredPeers); err != nil {
log.WithContext(ctx).Errorf("failed update expired peers: %s", err)
return nil, err
}
}
- if update.AutoGroups != nil && account.Settings.GroupsPropagationEnabled {
- removedGroups := difference(oldUser.AutoGroups, update.AutoGroups)
- // need force update all auto groups in any case they will not be duplicated
- account.UserGroupsAddToPeers(oldUser.Id, update.AutoGroups...)
- account.UserGroupsRemoveFromPeers(oldUser.Id, removedGroups...)
-
- account.Network.IncSerial()
- if err = am.Store.SaveAccount(ctx, account); err != nil {
- return nil, err
- }
+ account.Network.IncSerial()
+ if err = am.Store.SaveUsers(account.Id, account.Users); err != nil {
+ return nil, err
+ }
+ if account.Settings.GroupsPropagationEnabled {
am.updateAccountPeers(ctx, account)
- } else {
- if err = am.Store.SaveAccount(ctx, account); err != nil {
- return nil, err
- }
}
- defer func() {
- if oldUser.IsBlocked() != update.IsBlocked() {
- if update.IsBlocked() {
- am.StoreEvent(ctx, initiatorUserID, oldUser.Id, accountID, activity.UserBlocked, nil)
- } else {
- am.StoreEvent(ctx, initiatorUserID, oldUser.Id, accountID, activity.UserUnblocked, nil)
- }
- }
+ for _, storeEvent := range eventsToStore {
+ storeEvent()
+ }
+
+ return updatedUsers, nil
+}
+
+// prepareUserUpdateEvents prepares a list user update events based on the changes between the old and new user data.
+func (am *DefaultAccountManager) prepareUserUpdateEvents(ctx context.Context, initiatorUserID string, oldUser, newUser *User, account *Account, transferredOwnerRole bool) []func() {
+ var eventsToStore []func()
- switch {
- case transferedOwnerRole:
- am.StoreEvent(ctx, initiatorUserID, oldUser.Id, accountID, activity.TransferredOwnerRole, nil)
- case oldUser.Role != newUser.Role:
- am.StoreEvent(ctx, initiatorUserID, oldUser.Id, accountID, activity.UserRoleUpdated, map[string]any{"role": newUser.Role})
- default:
+ if oldUser.IsBlocked() != newUser.IsBlocked() {
+ if newUser.IsBlocked() {
+ eventsToStore = append(eventsToStore, func() {
+ am.StoreEvent(ctx, initiatorUserID, oldUser.Id, account.Id, activity.UserBlocked, nil)
+ })
+ } else {
+ eventsToStore = append(eventsToStore, func() {
+ am.StoreEvent(ctx, initiatorUserID, oldUser.Id, account.Id, activity.UserUnblocked, nil)
+ })
}
+ }
- if update.AutoGroups != nil {
- removedGroups := difference(oldUser.AutoGroups, update.AutoGroups)
- addedGroups := difference(newUser.AutoGroups, oldUser.AutoGroups)
- for _, g := range removedGroups {
- group := account.GetGroup(g)
- if group != nil {
- am.StoreEvent(ctx, initiatorUserID, oldUser.Id, accountID, activity.GroupRemovedFromUser,
+ switch {
+ case transferredOwnerRole:
+ eventsToStore = append(eventsToStore, func() {
+ am.StoreEvent(ctx, initiatorUserID, oldUser.Id, account.Id, activity.TransferredOwnerRole, nil)
+ })
+ case oldUser.Role != newUser.Role:
+ eventsToStore = append(eventsToStore, func() {
+ am.StoreEvent(ctx, initiatorUserID, oldUser.Id, account.Id, activity.UserRoleUpdated, map[string]any{"role": newUser.Role})
+ })
+ }
+
+ if newUser.AutoGroups != nil {
+ removedGroups := difference(oldUser.AutoGroups, newUser.AutoGroups)
+ addedGroups := difference(newUser.AutoGroups, oldUser.AutoGroups)
+ for _, g := range removedGroups {
+ group := account.GetGroup(g)
+ if group != nil {
+ eventsToStore = append(eventsToStore, func() {
+ am.StoreEvent(ctx, initiatorUserID, oldUser.Id, account.Id, activity.GroupRemovedFromUser,
map[string]any{"group": group.Name, "group_id": group.ID, "is_service_user": newUser.IsServiceUser, "user_name": newUser.ServiceUserName})
- } else {
- log.WithContext(ctx).Errorf("group %s not found while saving user activity event of account %s", g, account.Id)
- }
- }
+ })
- for _, g := range addedGroups {
- group := account.GetGroup(g)
- if group != nil {
- am.StoreEvent(ctx, initiatorUserID, oldUser.Id, accountID, activity.GroupAddedToUser,
+ } else {
+ log.WithContext(ctx).Errorf("group %s not found while saving user activity event of account %s", g, account.Id)
+ }
+ }
+ for _, g := range addedGroups {
+ group := account.GetGroup(g)
+ if group != nil {
+ eventsToStore = append(eventsToStore, func() {
+ am.StoreEvent(ctx, initiatorUserID, oldUser.Id, account.Id, activity.GroupAddedToUser,
map[string]any{"group": group.Name, "group_id": group.ID, "is_service_user": newUser.IsServiceUser, "user_name": newUser.ServiceUserName})
- }
+ })
}
}
- }()
+ }
+
+ return eventsToStore
+}
- if !isNil(am.idpManager) && !newUser.IsServiceUser {
- userData, err := am.lookupUserInCache(ctx, newUser.Id, account)
+func handleOwnerRoleTransfer(account *Account, initiatorUser, update *User) bool {
+ if initiatorUser.Role == UserRoleOwner && initiatorUser.Id != update.Id && update.Role == UserRoleOwner {
+ newInitiatorUser := initiatorUser.Copy()
+ newInitiatorUser.Role = UserRoleAdmin
+ account.Users[initiatorUser.Id] = newInitiatorUser
+ return true
+ }
+ return false
+}
+
+// getUserInfo retrieves the UserInfo for a given User and Account.
+// If the AccountManager has a non-nil idpManager and the User is not a service user,
+// it will attempt to look up the UserData from the cache.
+func getUserInfo(ctx context.Context, am *DefaultAccountManager, user *User, account *Account) (*UserInfo, error) {
+ if !isNil(am.idpManager) && !user.IsServiceUser {
+ userData, err := am.lookupUserInCache(ctx, user.Id, account)
if err != nil {
return nil, err
}
- return newUser.ToUserInfo(userData, account.Settings)
+ return user.ToUserInfo(userData, account.Settings)
+ }
+ return user.ToUserInfo(nil, account.Settings)
+}
+
+// validateUserUpdate validates the update operation for a user.
+func validateUserUpdate(account *Account, initiatorUser, oldUser, update *User) error {
+ if initiatorUser.HasAdminPower() && initiatorUser.Id == update.Id && oldUser.Blocked != update.Blocked {
+ return status.Errorf(status.PermissionDenied, "admins can't block or unblock themselves")
+ }
+ if initiatorUser.HasAdminPower() && initiatorUser.Id == update.Id && update.Role != initiatorUser.Role {
+ return status.Errorf(status.PermissionDenied, "admins can't change their role")
+ }
+ if initiatorUser.Role == UserRoleAdmin && oldUser.Role == UserRoleOwner && update.Role != oldUser.Role {
+ return status.Errorf(status.PermissionDenied, "only owners can remove owner role from their user")
+ }
+ if initiatorUser.Role == UserRoleAdmin && oldUser.Role == UserRoleOwner && update.IsBlocked() && !oldUser.IsBlocked() {
+ return status.Errorf(status.PermissionDenied, "unable to block owner user")
}
- return newUser.ToUserInfo(nil, account.Settings)
+ if initiatorUser.Role == UserRoleAdmin && update.Role == UserRoleOwner && update.Role != oldUser.Role {
+ return status.Errorf(status.PermissionDenied, "only owners can add owner role to other users")
+ }
+ if oldUser.IsServiceUser && update.Role == UserRoleOwner {
+ return status.Errorf(status.PermissionDenied, "can't update a service user with owner role")
+ }
+
+ for _, newGroupID := range update.AutoGroups {
+ if _, ok := account.Groups[newGroupID]; !ok {
+ return status.Errorf(status.InvalidArgument, "provided group ID %s in the user %s update doesn't exist",
+ newGroupID, update.Id)
+ }
+ }
+
+ return nil
}
// GetOrCreateAccountByUser returns an existing account for a given user id or creates a new one if doesn't exist
From 88d1c5a0fd57fe604c3a6eb7012e7f891bdd51c0 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Tue, 16 Jul 2024 10:14:30 +0200
Subject: [PATCH 13/77] fix forwarded metrics (#2273)
---
signal/server/signal.go | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/signal/server/signal.go b/signal/server/signal.go
index fc9c19efdbb..02c49c31d77 100644
--- a/signal/server/signal.go
+++ b/signal/server/signal.go
@@ -23,6 +23,8 @@ const (
labelTypeError = "error"
labelTypeNotConnected = "not_connected"
labelTypeNotRegistered = "not_registered"
+ labelTypeStream = "stream"
+ labelTypeMessage = "message"
labelError = "error"
labelErrorMissingId = "missing_id"
@@ -62,6 +64,7 @@ func (s *Server) Send(ctx context.Context, msg *proto.EncryptedMessage) (*proto.
}
if dstPeer, found := s.registry.Get(msg.RemoteKey); found {
+ start := time.Now()
//forward the message to the target peer
if err := dstPeer.Stream.Send(msg); err != nil {
log.Errorf("error while forwarding message from peer [%s] to peer [%s] %v", msg.Key, msg.RemoteKey, err)
@@ -69,6 +72,7 @@ func (s *Server) Send(ctx context.Context, msg *proto.EncryptedMessage) (*proto.
s.metrics.MessageForwardFailures.Add(ctx, 1, metric.WithAttributes(attribute.String(labelType, labelTypeError)))
} else {
+ s.metrics.MessageForwardLatency.Record(ctx, float64(time.Since(start).Nanoseconds())/1e6, metric.WithAttributes(attribute.String(labelType, labelTypeMessage)))
s.metrics.MessagesForwarded.Add(context.Background(), 1)
}
} else {
@@ -118,22 +122,21 @@ func (s *Server) ConnectStream(stream proto.SignalExchange_ConnectStreamServer)
} else if err != nil {
return err
}
- start := time.Now()
log.Debugf("received a new message from peer [%s] to peer [%s]", p.Id, msg.RemoteKey)
// lookup the target peer where the message is going to
if dstPeer, found := s.registry.Get(msg.RemoteKey); found {
+ start := time.Now()
//forward the message to the target peer
if err := dstPeer.Stream.Send(msg); err != nil {
log.Errorf("error while forwarding message from peer [%s] to peer [%s] %v", p.Id, msg.RemoteKey, err)
//todo respond to the sender?
-
+ s.metrics.MessageForwardFailures.Add(stream.Context(), 1, metric.WithAttributes(attribute.String(labelType, labelTypeError)))
+ } else {
// in milliseconds
- s.metrics.MessageForwardLatency.Record(stream.Context(), float64(time.Since(start).Nanoseconds())/1e6)
+ s.metrics.MessageForwardLatency.Record(stream.Context(), float64(time.Since(start).Nanoseconds())/1e6, metric.WithAttributes(attribute.String(labelType, labelTypeStream)))
s.metrics.MessagesForwarded.Add(stream.Context(), 1)
- } else {
- s.metrics.MessageForwardFailures.Add(stream.Context(), 1, metric.WithAttributes(attribute.String(labelType, labelTypeError)))
}
} else {
log.Debugf("message from peer [%s] can't be forwarded to peer [%s] because destination peer is not connected", p.Id, msg.RemoteKey)
From 12ff93ba7248c75b10b2f1c651ae06160fb717ea Mon Sep 17 00:00:00 2001
From: Carlos Hernandez
Date: Tue, 16 Jul 2024 02:19:01 -0600
Subject: [PATCH 14/77] Ignore no unique route updates (#2266)
---
client/internal/routemanager/client.go | 28 +++++++++++++++++++++-----
1 file changed, 23 insertions(+), 5 deletions(-)
diff --git a/client/internal/routemanager/client.go b/client/internal/routemanager/client.go
index 3c230df21eb..92c71b1e035 100644
--- a/client/internal/routemanager/client.go
+++ b/client/internal/routemanager/client.go
@@ -3,6 +3,7 @@ package routemanager
import (
"context"
"fmt"
+ "reflect"
"time"
"github.com/hashicorp/go-multierror"
@@ -309,22 +310,33 @@ func (c *clientNetwork) sendUpdateToClientNetworkWatcher(update routesUpdate) {
}()
}
-func (c *clientNetwork) handleUpdate(update routesUpdate) {
+func (c *clientNetwork) handleUpdate(update routesUpdate) bool {
+ isUpdateMapDifferent := false
updateMap := make(map[route.ID]*route.Route)
for _, r := range update.routes {
updateMap[r.ID] = r
}
+ if len(c.routes) != len(updateMap) {
+ isUpdateMapDifferent = true
+ }
+
for id, r := range c.routes {
_, found := updateMap[id]
if !found {
close(c.routePeersNotifiers[r.Peer])
delete(c.routePeersNotifiers, r.Peer)
+ isUpdateMapDifferent = true
+ continue
+ }
+ if !reflect.DeepEqual(c.routes[id], updateMap[id]) {
+ isUpdateMapDifferent = true
}
}
c.routes = updateMap
+ return isUpdateMapDifferent
}
// peersStateAndUpdateWatcher is the main point of reacting on client network routing events.
@@ -351,13 +363,19 @@ func (c *clientNetwork) peersStateAndUpdateWatcher() {
log.Debugf("Received a new client network route update for [%v]", c.handler)
- c.handleUpdate(update)
+ // hash update somehow
+ isTrueRouteUpdate := c.handleUpdate(update)
c.updateSerial = update.updateSerial
- err := c.recalculateRouteAndUpdatePeerAndSystem()
- if err != nil {
- log.Errorf("Failed to recalculate routes for network [%v]: %v", c.handler, err)
+ if isTrueRouteUpdate {
+ log.Debug("Client network update contains different routes, recalculating routes")
+ err := c.recalculateRouteAndUpdatePeerAndSystem()
+ if err != nil {
+ log.Errorf("Failed to recalculate routes for network [%v]: %v", c.handler, err)
+ }
+ } else {
+ log.Debug("Route update is not different, skipping route recalculation")
}
c.startPeersStatusChangeWatcher()
From 1d6f5482ddae3ae6805f279e180034a3b127c013 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20Ko=C5=82odziejczak?=
<31549762+mrl5@users.noreply.github.com>
Date: Tue, 16 Jul 2024 10:19:58 +0200
Subject: [PATCH 15/77] feat(client): send logs to syslog (#2259)
---
client/cmd/root.go | 2 +-
util/log.go | 6 +++++-
util/syslog_nonwindows.go | 20 ++++++++++++++++++++
util/syslog_windows.go | 3 +++
4 files changed, 29 insertions(+), 2 deletions(-)
create mode 100644 util/syslog_nonwindows.go
create mode 100644 util/syslog_windows.go
diff --git a/client/cmd/root.go b/client/cmd/root.go
index f0b5d2bdf4e..1e5c56366f2 100644
--- a/client/cmd/root.go
+++ b/client/cmd/root.go
@@ -121,7 +121,7 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&serviceName, "service", "s", defaultServiceName, "Netbird system service name")
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", defaultConfigPath, "Netbird config file location")
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", "info", "sets Netbird log level")
- rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the log will be output to stdout")
+ rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the log will be output to stdout. If syslog is specified the log will be sent to syslog daemon.")
rootCmd.PersistentFlags().StringVarP(&setupKey, "setup-key", "k", "", "Setup key obtained from the Management Service Dashboard (used to register peer)")
rootCmd.PersistentFlags().StringVar(&preSharedKey, preSharedKeyFlag, "", "Sets Wireguard PreSharedKey property. If set, then only peers that have the same key can communicate.")
rootCmd.PersistentFlags().StringVarP(&hostName, "hostname", "n", "", "Sets a custom hostname for the device")
diff --git a/util/log.go b/util/log.go
index 90ccea48fc0..11bb7efa78f 100644
--- a/util/log.go
+++ b/util/log.go
@@ -4,6 +4,7 @@ import (
"io"
"os"
"path/filepath"
+ "slices"
log "github.com/sirupsen/logrus"
"gopkg.in/natefinch/lumberjack.v2"
@@ -18,8 +19,9 @@ func InitLog(logLevel string, logPath string) error {
log.Errorf("Failed parsing log-level %s: %s", logLevel, err)
return err
}
+ custom_outputs := []string{"console", "syslog"};
- if logPath != "" && logPath != "console" {
+ if logPath != "" && !slices.Contains(custom_outputs, logPath) {
lumberjackLogger := &lumberjack.Logger{
// Log file absolute path, os agnostic
Filename: filepath.ToSlash(logPath),
@@ -29,6 +31,8 @@ func InitLog(logLevel string, logPath string) error {
Compress: true,
}
log.SetOutput(io.Writer(lumberjackLogger))
+ } else if logPath == "syslog" {
+ AddSyslogHook()
}
if os.Getenv("NB_LOG_FORMAT") == "json" {
diff --git a/util/syslog_nonwindows.go b/util/syslog_nonwindows.go
new file mode 100644
index 00000000000..6ffbcb8be25
--- /dev/null
+++ b/util/syslog_nonwindows.go
@@ -0,0 +1,20 @@
+//go:build !windows
+// +build !windows
+
+package util
+
+import (
+ "log/syslog"
+
+ log "github.com/sirupsen/logrus"
+ lSyslog "github.com/sirupsen/logrus/hooks/syslog"
+)
+
+func AddSyslogHook() {
+ hook, err := lSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
+
+ if err != nil {
+ log.Errorf("Failed creating syslog hook: %s", err)
+ }
+ log.AddHook(hook)
+}
diff --git a/util/syslog_windows.go b/util/syslog_windows.go
new file mode 100644
index 00000000000..a38d9005439
--- /dev/null
+++ b/util/syslog_windows.go
@@ -0,0 +1,3 @@
+package util
+
+func AddSyslogHook() {}
From f9c59a71316aa0f244a90e2564cdcb975e8c74b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20Ko=C5=82odziejczak?=
<31549762+mrl5@users.noreply.github.com>
Date: Tue, 16 Jul 2024 11:50:35 +0200
Subject: [PATCH 16/77] Refactor log util (#2276)
---
util/log.go | 4 ++--
util/syslog_windows.go | 5 ++++-
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/util/log.go b/util/log.go
index 11bb7efa78f..74b99311e1a 100644
--- a/util/log.go
+++ b/util/log.go
@@ -19,9 +19,9 @@ func InitLog(logLevel string, logPath string) error {
log.Errorf("Failed parsing log-level %s: %s", logLevel, err)
return err
}
- custom_outputs := []string{"console", "syslog"};
+ customOutputs := []string{"console", "syslog"};
- if logPath != "" && !slices.Contains(custom_outputs, logPath) {
+ if logPath != "" && !slices.Contains(customOutputs, logPath) {
lumberjackLogger := &lumberjack.Logger{
// Log file absolute path, os agnostic
Filename: filepath.ToSlash(logPath),
diff --git a/util/syslog_windows.go b/util/syslog_windows.go
index a38d9005439..171c1a45994 100644
--- a/util/syslog_windows.go
+++ b/util/syslog_windows.go
@@ -1,3 +1,6 @@
package util
-func AddSyslogHook() {}
+func AddSyslogHook() {
+ // The syslog package is not available for Windows. This adapter is needed
+ // to handle windows build.
+}
From 7c595e84934ae846d53dc0264b41614467c176f4 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Tue, 16 Jul 2024 15:36:51 +0200
Subject: [PATCH 17/77] Add get_registration_delay_milliseconds metric (#2275)
---
signal/README.md | 3 +++
signal/metrics/app.go | 8 ++++++++
signal/server/signal.go | 15 +++++++++++++--
3 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/signal/README.md b/signal/README.md
index dd2d761ad90..2da47283e37 100644
--- a/signal/README.md
+++ b/signal/README.md
@@ -90,6 +90,9 @@ The Signal Server exposes the following metrics in Prometheus format:
- **registration_delay_milliseconds**: A Histogram metric that measures the time
it took to register a peer in
milliseconds.
+- **get_registration_delay_milliseconds**: A Histogram metric that measures the time
+ it took to get a peer registration in
+ milliseconds.
- **messages_forwarded_total**: A Counter metric that counts the total number of
messages forwarded between peers.
- **message_forward_failures_total**: A Counter metric that counts the total
diff --git a/signal/metrics/app.go b/signal/metrics/app.go
index fb882a5d4c5..f8be88be773 100644
--- a/signal/metrics/app.go
+++ b/signal/metrics/app.go
@@ -15,6 +15,7 @@ type AppMetrics struct {
Deregistrations metric.Int64Counter
RegistrationFailures metric.Int64Counter
RegistrationDelay metric.Float64Histogram
+ GetRegistrationDelay metric.Float64Histogram
MessagesForwarded metric.Int64Counter
MessageForwardFailures metric.Int64Counter
@@ -54,6 +55,12 @@ func NewAppMetrics(meter metric.Meter) (*AppMetrics, error) {
return nil, err
}
+ getRegistrationDelay, err := meter.Float64Histogram("get_registration_delay_milliseconds",
+ metric.WithExplicitBucketBoundaries(getStandardBucketBoundaries()...))
+ if err != nil {
+ return nil, err
+ }
+
messagesForwarded, err := meter.Int64Counter("messages_forwarded_total")
if err != nil {
return nil, err
@@ -80,6 +87,7 @@ func NewAppMetrics(meter metric.Meter) (*AppMetrics, error) {
Deregistrations: deregistrations,
RegistrationFailures: registrationFailures,
RegistrationDelay: registrationDelay,
+ GetRegistrationDelay: getRegistrationDelay,
MessagesForwarded: messagesForwarded,
MessageForwardFailures: messageForwardFailures,
diff --git a/signal/server/signal.go b/signal/server/signal.go
index 02c49c31d77..4ececafff9b 100644
--- a/signal/server/signal.go
+++ b/signal/server/signal.go
@@ -30,6 +30,10 @@ const (
labelErrorMissingId = "missing_id"
labelErrorMissingMeta = "missing_meta"
labelErrorFailedHeader = "failed_header"
+
+ labelRegistrionStatus = "status"
+ labelRegistrationFound = "found"
+ labelRegistrationNotFound = "not_found"
)
// Server an instance of a Signal server
@@ -63,7 +67,10 @@ func (s *Server) Send(ctx context.Context, msg *proto.EncryptedMessage) (*proto.
return nil, fmt.Errorf("peer %s is not registered", msg.Key)
}
+ getRegistrationStart := time.Now()
+
if dstPeer, found := s.registry.Get(msg.RemoteKey); found {
+ s.metrics.GetRegistrationDelay.Record(ctx, float64(time.Since(getRegistrationStart).Nanoseconds())/1e6, metric.WithAttributes(attribute.String(labelType, labelTypeMessage), attribute.String(labelRegistrionStatus, labelRegistrationFound)))
start := time.Now()
//forward the message to the target peer
if err := dstPeer.Stream.Send(msg); err != nil {
@@ -76,6 +83,7 @@ func (s *Server) Send(ctx context.Context, msg *proto.EncryptedMessage) (*proto.
s.metrics.MessagesForwarded.Add(context.Background(), 1)
}
} else {
+ s.metrics.GetRegistrationDelay.Record(ctx, float64(time.Since(getRegistrationStart).Nanoseconds())/1e6, metric.WithAttributes(attribute.String(labelType, labelTypeMessage), attribute.String(labelRegistrionStatus, labelRegistrationNotFound)))
log.Debugf("message from peer [%s] can't be forwarded to peer [%s] because destination peer is not connected", msg.Key, msg.RemoteKey)
//todo respond to the sender?
@@ -125,8 +133,11 @@ func (s *Server) ConnectStream(stream proto.SignalExchange_ConnectStreamServer)
log.Debugf("received a new message from peer [%s] to peer [%s]", p.Id, msg.RemoteKey)
+ getRegistrationStart := time.Now()
+
// lookup the target peer where the message is going to
if dstPeer, found := s.registry.Get(msg.RemoteKey); found {
+ s.metrics.GetRegistrationDelay.Record(stream.Context(), float64(time.Since(getRegistrationStart).Nanoseconds())/1e6, metric.WithAttributes(attribute.String(labelType, labelTypeStream), attribute.String(labelRegistrionStatus, labelRegistrationFound)))
start := time.Now()
//forward the message to the target peer
if err := dstPeer.Stream.Send(msg); err != nil {
@@ -139,10 +150,10 @@ func (s *Server) ConnectStream(stream proto.SignalExchange_ConnectStreamServer)
s.metrics.MessagesForwarded.Add(stream.Context(), 1)
}
} else {
+ s.metrics.GetRegistrationDelay.Record(stream.Context(), float64(time.Since(getRegistrationStart).Nanoseconds())/1e6, metric.WithAttributes(attribute.String(labelType, labelTypeStream), attribute.String(labelRegistrionStatus, labelRegistrationNotFound)))
+ s.metrics.MessageForwardFailures.Add(stream.Context(), 1, metric.WithAttributes(attribute.String(labelType, labelTypeNotConnected)))
log.Debugf("message from peer [%s] can't be forwarded to peer [%s] because destination peer is not connected", p.Id, msg.RemoteKey)
//todo respond to the sender?
-
- s.metrics.MessageForwardFailures.Add(stream.Context(), 1, metric.WithAttributes(attribute.String(labelType, labelTypeNotConnected)))
}
}
<-stream.Context().Done()
From 668d229b67383d9af9e11430b07c366e046f50af Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Tue, 16 Jul 2024 16:55:57 +0200
Subject: [PATCH 18/77] Fix metric label typo (#2278)
---
signal/server/signal.go | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/signal/server/signal.go b/signal/server/signal.go
index 4ececafff9b..219bdcc4143 100644
--- a/signal/server/signal.go
+++ b/signal/server/signal.go
@@ -31,7 +31,7 @@ const (
labelErrorMissingMeta = "missing_meta"
labelErrorFailedHeader = "failed_header"
- labelRegistrionStatus = "status"
+ labelRegistrationStatus = "status"
labelRegistrationFound = "found"
labelRegistrationNotFound = "not_found"
)
@@ -70,7 +70,7 @@ func (s *Server) Send(ctx context.Context, msg *proto.EncryptedMessage) (*proto.
getRegistrationStart := time.Now()
if dstPeer, found := s.registry.Get(msg.RemoteKey); found {
- s.metrics.GetRegistrationDelay.Record(ctx, float64(time.Since(getRegistrationStart).Nanoseconds())/1e6, metric.WithAttributes(attribute.String(labelType, labelTypeMessage), attribute.String(labelRegistrionStatus, labelRegistrationFound)))
+ s.metrics.GetRegistrationDelay.Record(ctx, float64(time.Since(getRegistrationStart).Nanoseconds())/1e6, metric.WithAttributes(attribute.String(labelType, labelTypeMessage), attribute.String(labelRegistrationStatus, labelRegistrationFound)))
start := time.Now()
//forward the message to the target peer
if err := dstPeer.Stream.Send(msg); err != nil {
@@ -83,7 +83,7 @@ func (s *Server) Send(ctx context.Context, msg *proto.EncryptedMessage) (*proto.
s.metrics.MessagesForwarded.Add(context.Background(), 1)
}
} else {
- s.metrics.GetRegistrationDelay.Record(ctx, float64(time.Since(getRegistrationStart).Nanoseconds())/1e6, metric.WithAttributes(attribute.String(labelType, labelTypeMessage), attribute.String(labelRegistrionStatus, labelRegistrationNotFound)))
+ s.metrics.GetRegistrationDelay.Record(ctx, float64(time.Since(getRegistrationStart).Nanoseconds())/1e6, metric.WithAttributes(attribute.String(labelType, labelTypeMessage), attribute.String(labelRegistrationStatus, labelRegistrationNotFound)))
log.Debugf("message from peer [%s] can't be forwarded to peer [%s] because destination peer is not connected", msg.Key, msg.RemoteKey)
//todo respond to the sender?
@@ -137,7 +137,7 @@ func (s *Server) ConnectStream(stream proto.SignalExchange_ConnectStreamServer)
// lookup the target peer where the message is going to
if dstPeer, found := s.registry.Get(msg.RemoteKey); found {
- s.metrics.GetRegistrationDelay.Record(stream.Context(), float64(time.Since(getRegistrationStart).Nanoseconds())/1e6, metric.WithAttributes(attribute.String(labelType, labelTypeStream), attribute.String(labelRegistrionStatus, labelRegistrationFound)))
+ s.metrics.GetRegistrationDelay.Record(stream.Context(), float64(time.Since(getRegistrationStart).Nanoseconds())/1e6, metric.WithAttributes(attribute.String(labelType, labelTypeStream), attribute.String(labelRegistrationStatus, labelRegistrationFound)))
start := time.Now()
//forward the message to the target peer
if err := dstPeer.Stream.Send(msg); err != nil {
@@ -150,7 +150,7 @@ func (s *Server) ConnectStream(stream proto.SignalExchange_ConnectStreamServer)
s.metrics.MessagesForwarded.Add(stream.Context(), 1)
}
} else {
- s.metrics.GetRegistrationDelay.Record(stream.Context(), float64(time.Since(getRegistrationStart).Nanoseconds())/1e6, metric.WithAttributes(attribute.String(labelType, labelTypeStream), attribute.String(labelRegistrionStatus, labelRegistrationNotFound)))
+ s.metrics.GetRegistrationDelay.Record(stream.Context(), float64(time.Since(getRegistrationStart).Nanoseconds())/1e6, metric.WithAttributes(attribute.String(labelType, labelTypeStream), attribute.String(labelRegistrationStatus, labelRegistrationNotFound)))
s.metrics.MessageForwardFailures.Add(stream.Context(), 1, metric.WithAttributes(attribute.String(labelType, labelTypeNotConnected)))
log.Debugf("message from peer [%s] can't be forwarded to peer [%s] because destination peer is not connected", p.Id, msg.RemoteKey)
//todo respond to the sender?
From a711e116a3f59edcae6f3197d20723511001da7d Mon Sep 17 00:00:00 2001
From: ctrl-zzz <78654296+ctrl-zzz@users.noreply.github.com>
Date: Tue, 16 Jul 2024 17:38:12 +0200
Subject: [PATCH 19/77] fix: save peer status correctly in sqlstore (#2262)
* fix: save peer status correctly in sqlstore
https://github.com/netbirdio/netbird/issues/2110#issuecomment-2162768273
* feat: update test function
* refactor: simplify status update
---
management/server/sql_store.go | 9 +++++++--
management/server/sql_store_test.go | 4 ++--
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/management/server/sql_store.go b/management/server/sql_store.go
index 41e9fde8b20..37cc10d8bb4 100644
--- a/management/server/sql_store.go
+++ b/management/server/sql_store.go
@@ -274,10 +274,15 @@ func (s *SqlStore) GetInstallationID() string {
func (s *SqlStore) SavePeerStatus(accountID, peerID string, peerStatus nbpeer.PeerStatus) error {
var peerCopy nbpeer.Peer
peerCopy.Status = &peerStatus
+
+ fieldsToUpdate := []string{
+ "peer_status_last_seen", "peer_status_connected",
+ "peer_status_login_expired", "peer_status_required_approval",
+ }
result := s.db.Model(&nbpeer.Peer{}).
+ Select(fieldsToUpdate).
Where("account_id = ? AND id = ?", accountID, peerID).
- Updates(peerCopy)
-
+ Updates(&peerCopy)
if result.Error != nil {
return result.Error
}
diff --git a/management/server/sql_store_test.go b/management/server/sql_store_test.go
index e3ba00b5605..f46ca7e5db1 100644
--- a/management/server/sql_store_test.go
+++ b/management/server/sql_store_test.go
@@ -373,7 +373,7 @@ func TestSqlite_SavePeerStatus(t *testing.T) {
require.NoError(t, err)
// save status of non-existing peer
- newStatus := nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()}
+ newStatus := nbpeer.PeerStatus{Connected: false, LastSeen: time.Now().UTC()}
err = store.SavePeerStatus(account.Id, "non-existing-peer", newStatus)
assert.Error(t, err)
parsedErr, ok := status.FromError(err)
@@ -388,7 +388,7 @@ func TestSqlite_SavePeerStatus(t *testing.T) {
IP: net.IP{127, 0, 0, 1},
Meta: nbpeer.PeerSystemMeta{},
Name: "peer name",
- Status: &nbpeer.PeerStatus{Connected: false, LastSeen: time.Now().UTC()},
+ Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()},
}
err = store.SaveAccount(context.Background(), account)
From 4fad0e521f59ffdbccb26c10e5b0601e4e57f2cb Mon Sep 17 00:00:00 2001
From: benniekiss <63211101+benniekiss@users.noreply.github.com>
Date: Tue, 16 Jul 2024 14:44:21 -0400
Subject: [PATCH 20/77] Support custom SSL certificates for the signal service
(#2257)
---
signal/README.md | 2 ++
signal/cmd/run.go | 78 ++++++++++++++++++++++++++++++++++++-----------
2 files changed, 62 insertions(+), 18 deletions(-)
diff --git a/signal/README.md b/signal/README.md
index 2da47283e37..9e3207cfad0 100644
--- a/signal/README.md
+++ b/signal/README.md
@@ -18,6 +18,8 @@ Flags:
--letsencrypt-domain string a domain to issue Let's Encrypt certificate for. Enables TLS using Let's Encrypt. Will fetch and renew certificate, and run the server with TLS
--port int Server port to listen on (e.g. 10000) (default 10000)
--ssl-dir string server ssl directory location. *Required only for Let's Encrypt certificates. (default "/var/lib/netbird/")
+ --cert-file string Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect
+ --cert-key string Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect
Global Flags:
--log-file string sets Netbird log path. If console is specified the the log will be output to stdout (default "/var/log/netbird/signal.log")
diff --git a/signal/cmd/run.go b/signal/cmd/run.go
index 4b0dc583e9f..8f75c1e04ab 100644
--- a/signal/cmd/run.go
+++ b/signal/cmd/run.go
@@ -2,6 +2,7 @@ package cmd
import (
"context"
+ "crypto/tls"
"errors"
"flag"
"fmt"
@@ -41,7 +42,8 @@ var (
signalLetsencryptDomain string
signalSSLDir string
defaultSignalSSLDir string
- tlsEnabled bool
+ signalCertFile string
+ signalCertKey string
signalKaep = grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: 5 * time.Second,
@@ -59,9 +61,13 @@ var (
Use: "run",
Short: "start NetBird Signal Server daemon",
PreRun: func(cmd *cobra.Command, args []string) {
+ flag.Parse()
+
// detect whether user specified a port
userPort := cmd.Flag("port").Changed
- if signalLetsencryptDomain != "" {
+
+ tlsEnabled := false
+ if signalLetsencryptDomain != "" || (signalCertFile != "" && signalCertKey != "") {
tlsEnabled = true
}
@@ -93,14 +99,24 @@ var (
var opts []grpc.ServerOption
var certManager *autocert.Manager
- if tlsEnabled {
- // Let's encrypt enabled -> generate certificate automatically
+ var tlsConfig *tls.Config
+ if signalLetsencryptDomain != "" {
certManager, err = encryption.CreateCertManager(signalSSLDir, signalLetsencryptDomain)
if err != nil {
return err
}
transportCredentials := credentials.NewTLS(certManager.TLSConfig())
opts = append(opts, grpc.Creds(transportCredentials))
+ log.Infof("setting up TLS with LetsEncrypt.")
+ } else if signalCertFile != "" && signalCertKey != "" {
+ tlsConfig, err = loadTLSConfig(signalCertFile, signalCertKey)
+ if err != nil {
+ log.Errorf("cannot load TLS credentials: %v", err)
+ return err
+ }
+ transportCredentials := credentials.NewTLS(tlsConfig)
+ opts = append(opts, grpc.Creds(transportCredentials))
+ log.Infof("setting up TLS with custom certificates.")
}
metricsServer := metrics.NewServer(metricsPort, "")
@@ -124,32 +140,27 @@ var (
}
proto.RegisterSignalExchangeServer(grpcServer, srv)
+ grpcRootHandler := grpcHandlerFunc(grpcServer)
var compatListener net.Listener
- if signalPort != 10000 {
- // The Signal gRPC server was running on port 10000 previously. Old agents that are already connected to Signal
- // are using port 10000. For compatibility purposes we keep running a 2nd gRPC server on port 10000.
- compatListener, err = serveGRPC(grpcServer, 10000)
- if err != nil {
- return err
- }
- log.Infof("running gRPC backward compatibility server: %s", compatListener.Addr().String())
- }
-
var grpcListener net.Listener
var httpListener net.Listener
- if tlsEnabled {
- httpListener = certManager.Listener()
+
+ if certManager != nil {
+ // a call to certManager.Listener() always creates a new listener so we do it once
+ httpListener := certManager.Listener()
if signalPort == 443 {
// running gRPC and HTTP cert manager on the same port
- serveHTTP(httpListener, certManager.HTTPHandler(grpcHandlerFunc(grpcServer)))
+ serveHTTP(httpListener, certManager.HTTPHandler(grpcRootHandler))
log.Infof("running HTTP server (LetsEncrypt challenge handler) and gRPC server on the same port: %s", httpListener.Addr().String())
} else {
+ // Start the HTTP cert manager server separately
serveHTTP(httpListener, certManager.HTTPHandler(nil))
log.Infof("running HTTP server (LetsEncrypt challenge handler): %s", httpListener.Addr().String())
}
}
- if signalPort != 443 || !tlsEnabled {
+ // If certManager is configured and signalPort == 443, then the gRPC server has already been started
+ if certManager == nil || signalPort != 443 {
grpcListener, err = serveGRPC(grpcServer, signalPort)
if err != nil {
return err
@@ -157,6 +168,16 @@ var (
log.Infof("running gRPC server: %s", grpcListener.Addr().String())
}
+ if signalPort != 10000 {
+ // The Signal gRPC server was running on port 10000 previously. Old agents that are already connected to Signal
+ // are using port 10000. For compatibility purposes we keep running a 2nd gRPC server on port 10000.
+ compatListener, err = serveGRPC(grpcServer, 10000)
+ if err != nil {
+ return err
+ }
+ log.Infof("running gRPC backward compatibility server: %s", compatListener.Addr().String())
+ }
+
log.Infof("signal server version %s", version.NetbirdVersion())
log.Infof("started Signal Service")
@@ -232,6 +253,25 @@ func serveGRPC(grpcServer *grpc.Server, port int) (net.Listener, error) {
return listener, nil
}
+func loadTLSConfig(certFile string, certKey string) (*tls.Config, error) {
+ // Load server's certificate and private key
+ serverCert, err := tls.LoadX509KeyPair(certFile, certKey)
+ if err != nil {
+ return nil, err
+ }
+
+ // NewDefaultAppMetrics the credentials and return it
+ config := &tls.Config{
+ Certificates: []tls.Certificate{serverCert},
+ ClientAuth: tls.NoClientCert,
+ NextProtos: []string{
+ "h2", "http/1.1", // enable HTTP/2
+ },
+ }
+
+ return config, nil
+}
+
func cpFile(src, dst string) error {
var err error
var srcfd *os.File
@@ -323,4 +363,6 @@ func init() {
runCmd.PersistentFlags().IntVar(&signalPort, "port", 80, "Server port to listen on (defaults to 443 if TLS is enabled, 80 otherwise")
runCmd.Flags().StringVar(&signalSSLDir, "ssl-dir", defaultSignalSSLDir, "server ssl directory location. *Required only for Let's Encrypt certificates.")
runCmd.Flags().StringVar(&signalLetsencryptDomain, "letsencrypt-domain", "", "a domain to issue Let's Encrypt certificate for. Enables TLS using Let's Encrypt. Will fetch and renew certificate, and run the server with TLS")
+ runCmd.Flags().StringVar(&signalCertFile, "cert-file", "", "Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
+ runCmd.Flags().StringVar(&signalCertKey, "cert-key", "", "Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
}
From 95d725f2c19d49a5993cac8b81a6cc11b21dd573 Mon Sep 17 00:00:00 2001
From: pascal-fischer <32096965+pascal-fischer@users.noreply.github.com>
Date: Wed, 17 Jul 2024 16:26:06 +0200
Subject: [PATCH 21/77] Wait on daemon down (#2279)
---
client/cmd/down.go | 2 +-
client/internal/engine.go | 36 ++++++++++-
client/internal/networkmonitor/monitor_bsd.go | 16 ++++-
client/server/server.go | 22 ++++++-
management/client/grpc.go | 64 ++++++++++---------
signal/client/grpc.go | 35 ++++------
util/grpc/dialer.go | 43 +++++++++++++
7 files changed, 159 insertions(+), 59 deletions(-)
diff --git a/client/cmd/down.go b/client/cmd/down.go
index 1837b13da5f..4d9f1eba4c2 100644
--- a/client/cmd/down.go
+++ b/client/cmd/down.go
@@ -26,7 +26,7 @@ var downCmd = &cobra.Command{
return err
}
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*7)
defer cancel()
conn, err := DialClientGRPCServer(ctx, daemonAddr)
diff --git a/client/internal/engine.go b/client/internal/engine.go
index 21a765a96e4..9e275c00737 100644
--- a/client/internal/engine.go
+++ b/client/internal/engine.go
@@ -266,8 +266,23 @@ func (e *Engine) Stop() error {
e.close()
e.wgConnWorker.Wait()
- log.Infof("stopped Netbird Engine")
- return nil
+
+ maxWaitTime := 5 * time.Second
+ timeout := time.After(maxWaitTime)
+
+ for {
+ if !e.IsWGIfaceUp() {
+ log.Infof("stopped Netbird Engine")
+ return nil
+ }
+
+ select {
+ case <-timeout:
+ return fmt.Errorf("timeout when waiting for interface shutdown")
+ default:
+ time.Sleep(100 * time.Millisecond)
+ }
+ }
}
// Start creates a new WireGuard tunnel interface and listens to events from Signal and Management services
@@ -1533,3 +1548,20 @@ func isChecksEqual(checks []*mgmProto.Checks, oChecks []*mgmProto.Checks) bool {
return slices.Equal(checks.Files, oChecks.Files)
})
}
+
+func (e *Engine) IsWGIfaceUp() bool {
+ if e == nil || e.wgInterface == nil {
+ return false
+ }
+ iface, err := net.InterfaceByName(e.wgInterface.Name())
+ if err != nil {
+ log.Debugf("failed to get interface by name %s: %v", e.wgInterface.Name(), err)
+ return false
+ }
+
+ if iface.Flags&net.FlagUp != 0 {
+ return true
+ }
+
+ return false
+}
diff --git a/client/internal/networkmonitor/monitor_bsd.go b/client/internal/networkmonitor/monitor_bsd.go
index 8d6ccd51ba0..29df7ea7f7d 100644
--- a/client/internal/networkmonitor/monitor_bsd.go
+++ b/client/internal/networkmonitor/monitor_bsd.go
@@ -4,6 +4,7 @@ package networkmonitor
import (
"context"
+ "errors"
"fmt"
"syscall"
"unsafe"
@@ -21,11 +22,20 @@ func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, ca
return fmt.Errorf("failed to open routing socket: %v", err)
}
defer func() {
- if err := unix.Close(fd); err != nil {
+ err := unix.Close(fd)
+ if err != nil && !errors.Is(err, unix.EBADF) {
log.Errorf("Network monitor: failed to close routing socket: %v", err)
}
}()
+ go func() {
+ <-ctx.Done()
+ err := unix.Close(fd)
+ if err != nil && !errors.Is(err, unix.EBADF) {
+ log.Debugf("Network monitor: closed routing socket")
+ }
+ }()
+
for {
select {
case <-ctx.Done():
@@ -34,7 +44,9 @@ func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, ca
buf := make([]byte, 2048)
n, err := unix.Read(fd, buf)
if err != nil {
- log.Errorf("Network monitor: failed to read from routing socket: %v", err)
+ if !errors.Is(err, unix.EBADF) && !errors.Is(err, unix.EINVAL) {
+ log.Errorf("Network monitor: failed to read from routing socket: %v", err)
+ }
continue
}
if n < unix.SizeofRtMsghdr {
diff --git a/client/server/server.go b/client/server/server.go
index 2805c10f4f6..8173d07413f 100644
--- a/client/server/server.go
+++ b/client/server/server.go
@@ -582,7 +582,7 @@ func (s *Server) Up(callerCtx context.Context, _ *proto.UpRequest) (*proto.UpRes
}
// Down engine work in the daemon.
-func (s *Server) Down(_ context.Context, _ *proto.DownRequest) (*proto.DownResponse, error) {
+func (s *Server) Down(ctx context.Context, _ *proto.DownRequest) (*proto.DownResponse, error) {
s.mutex.Lock()
defer s.mutex.Unlock()
@@ -593,7 +593,25 @@ func (s *Server) Down(_ context.Context, _ *proto.DownRequest) (*proto.DownRespo
state := internal.CtxGetState(s.rootCtx)
state.Set(internal.StatusIdle)
- return &proto.DownResponse{}, nil
+ maxWaitTime := 5 * time.Second
+ timeout := time.After(maxWaitTime)
+
+ engine := s.connectClient.Engine()
+
+ for {
+ if !engine.IsWGIfaceUp() {
+ return &proto.DownResponse{}, nil
+ }
+
+ select {
+ case <-ctx.Done():
+ return &proto.DownResponse{}, nil
+ case <-timeout:
+ return nil, fmt.Errorf("failed to shut down properly")
+ default:
+ time.Sleep(100 * time.Millisecond)
+ }
+ }
}
// Status returns the daemon status
diff --git a/management/client/grpc.go b/management/client/grpc.go
index a8f4a91c787..568c1531339 100644
--- a/management/client/grpc.go
+++ b/management/client/grpc.go
@@ -2,7 +2,6 @@ package client
import (
"context"
- "crypto/tls"
"fmt"
"io"
"sync"
@@ -11,15 +10,11 @@ import (
"google.golang.org/grpc/codes"
gstatus "google.golang.org/grpc/status"
+ "github.com/cenkalti/backoff/v4"
log "github.com/sirupsen/logrus"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
- "google.golang.org/grpc/credentials"
- "google.golang.org/grpc/credentials/insecure"
- "google.golang.org/grpc/keepalive"
-
- "github.com/cenkalti/backoff/v4"
"github.com/netbirdio/netbird/client/system"
"github.com/netbirdio/netbird/encryption"
@@ -51,26 +46,21 @@ type GrpcClient struct {
// NewClient creates a new client to Management service
func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*GrpcClient, error) {
- transportOption := grpc.WithTransportCredentials(insecure.NewCredentials())
+ var conn *grpc.ClientConn
- if tlsEnabled {
- transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
+ operation := func() error {
+ var err error
+ conn, err = nbgrpc.CreateConnection(addr, tlsEnabled)
+ if err != nil {
+ log.Printf("createConnection error: %v", err)
+ return err
+ }
+ return nil
}
- mgmCtx, cancel := context.WithTimeout(ctx, ConnectTimeout)
- defer cancel()
- conn, err := grpc.DialContext(
- mgmCtx,
- addr,
- transportOption,
- nbgrpc.WithCustomDialer(),
- grpc.WithBlock(),
- grpc.WithKeepaliveParams(keepalive.ClientParameters{
- Time: 30 * time.Second,
- Timeout: 10 * time.Second,
- }))
+ err := backoff.Retry(operation, nbgrpc.Backoff(ctx))
if err != nil {
- log.Errorf("failed creating connection to Management Service %v", err)
+ log.Errorf("failed creating connection to Management Service: %v", err)
return nil, err
}
@@ -326,25 +316,41 @@ func (c *GrpcClient) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*pro
if !c.ready() {
return nil, fmt.Errorf(errMsgNoMgmtConnection)
}
+
loginReq, err := encryption.EncryptMessage(serverKey, c.key, req)
if err != nil {
log.Errorf("failed to encrypt message: %s", err)
return nil, err
}
- mgmCtx, cancel := context.WithTimeout(c.ctx, ConnectTimeout)
- defer cancel()
- resp, err := c.realClient.Login(mgmCtx, &proto.EncryptedMessage{
- WgPubKey: c.key.PublicKey().String(),
- Body: loginReq,
- })
+
+ var resp *proto.EncryptedMessage
+ operation := func() error {
+ mgmCtx, cancel := context.WithTimeout(context.Background(), ConnectTimeout)
+ defer cancel()
+
+ var err error
+ resp, err = c.realClient.Login(mgmCtx, &proto.EncryptedMessage{
+ WgPubKey: c.key.PublicKey().String(),
+ Body: loginReq,
+ })
+ if err != nil {
+ log.Printf("Login error: %v", err)
+ return err
+ }
+
+ return nil
+ }
+
+ err = backoff.Retry(operation, nbgrpc.Backoff(c.ctx))
if err != nil {
+ log.Errorf("failed to login to Management Service: %v", err)
return nil, err
}
loginResp := &proto.LoginResponse{}
err = encryption.DecryptMessage(serverKey, c.key, resp.Body, loginResp)
if err != nil {
- log.Errorf("failed to decrypt registration message: %s", err)
+ log.Errorf("failed to decrypt login response: %s", err)
return nil, err
}
diff --git a/signal/client/grpc.go b/signal/client/grpc.go
index c6f03ec8604..7a3b502ffc6 100644
--- a/signal/client/grpc.go
+++ b/signal/client/grpc.go
@@ -2,7 +2,6 @@ package client
import (
"context"
- "crypto/tls"
"fmt"
"io"
"sync"
@@ -14,9 +13,6 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/connectivity"
- "google.golang.org/grpc/credentials"
- "google.golang.org/grpc/credentials/insecure"
- "google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
@@ -64,28 +60,21 @@ func (c *GrpcClient) Close() error {
// NewClient creates a new Signal client
func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled bool) (*GrpcClient, error) {
+ var conn *grpc.ClientConn
- transportOption := grpc.WithTransportCredentials(insecure.NewCredentials())
-
- if tlsEnabled {
- transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
+ operation := func() error {
+ var err error
+ conn, err = nbgrpc.CreateConnection(addr, tlsEnabled)
+ if err != nil {
+ log.Printf("createConnection error: %v", err)
+ return err
+ }
+ return nil
}
- sigCtx, cancel := context.WithTimeout(ctx, client.ConnectTimeout)
- defer cancel()
- conn, err := grpc.DialContext(
- sigCtx,
- addr,
- transportOption,
- nbgrpc.WithCustomDialer(),
- grpc.WithBlock(),
- grpc.WithKeepaliveParams(keepalive.ClientParameters{
- Time: 30 * time.Second,
- Timeout: 10 * time.Second,
- }))
-
+ err := backoff.Retry(operation, nbgrpc.Backoff(ctx))
if err != nil {
- log.Errorf("failed to connect to the signalling server %v", err)
+ log.Errorf("failed to connect to the signalling server: %v", err)
return nil, err
}
@@ -408,7 +397,7 @@ func (c *GrpcClient) receive(stream proto.SignalExchange_ConnectStreamClient,
if err != nil {
log.Errorf("error while handling message of Peer [key: %s] error: [%s]", msg.Key, err.Error())
- //todo send something??
+ // todo send something??
}
}
}
diff --git a/util/grpc/dialer.go b/util/grpc/dialer.go
index 3fba0c84e35..57ab8fd550d 100644
--- a/util/grpc/dialer.go
+++ b/util/grpc/dialer.go
@@ -2,12 +2,18 @@ package grpc
import (
"context"
+ "crypto/tls"
"net"
"os/user"
"runtime"
+ "time"
+ "github.com/cenkalti/backoff/v4"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
+ "google.golang.org/grpc/credentials"
+ "google.golang.org/grpc/credentials/insecure"
+ "google.golang.org/grpc/keepalive"
nbnet "github.com/netbirdio/netbird/util/net"
)
@@ -35,3 +41,40 @@ func WithCustomDialer() grpc.DialOption {
return conn, nil
})
}
+
+// grpcDialBackoff is the backoff mechanism for the grpc calls
+func Backoff(ctx context.Context) backoff.BackOff {
+ b := backoff.NewExponentialBackOff()
+ b.MaxElapsedTime = 10 * time.Second
+ b.Clock = backoff.SystemClock
+ return backoff.WithContext(b, ctx)
+}
+
+func CreateConnection(addr string, tlsEnabled bool) (*grpc.ClientConn, error) {
+ transportOption := grpc.WithTransportCredentials(insecure.NewCredentials())
+
+ if tlsEnabled {
+ transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
+ }
+
+ connCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+
+ conn, err := grpc.DialContext(
+ connCtx,
+ addr,
+ transportOption,
+ WithCustomDialer(),
+ grpc.WithBlock(),
+ grpc.WithKeepaliveParams(keepalive.ClientParameters{
+ Time: 30 * time.Second,
+ Timeout: 10 * time.Second,
+ }),
+ )
+ if err != nil {
+ log.Printf("DialContext error: %v", err)
+ return nil, err
+ }
+
+ return conn, nil
+}
From e78ec2e985111dc3b1470c199f2093804a317abb Mon Sep 17 00:00:00 2001
From: Viktor Liu <17948409+lixmal@users.noreply.github.com>
Date: Wed, 17 Jul 2024 19:50:06 +0200
Subject: [PATCH 22/77] Don't add exclusion routes for IPs that are part of
connected networks (#2258)
This prevents arp/ndp issues on macOS leading to unreachability of that IP.
---
.../systemops/systemops_generic.go | 37 ++++++++-
.../systemops/systemops_windows_test.go | 75 ++++++-------------
2 files changed, 59 insertions(+), 53 deletions(-)
diff --git a/client/internal/routemanager/systemops/systemops_generic.go b/client/internal/routemanager/systemops/systemops_generic.go
index 0d1c16ca1f9..615e1b528b4 100644
--- a/client/internal/routemanager/systemops/systemops_generic.go
+++ b/client/internal/routemanager/systemops/systemops_generic.go
@@ -50,7 +50,7 @@ func (r *SysOps) setupRefCounter(initAddresses []net.IP) (nbnet.AddHookFunc, nbn
nexthop, err := r.addRouteToNonVPNIntf(prefix, r.wgInterface, initialNexthop)
if errors.Is(err, vars.ErrRouteNotAllowed) || errors.Is(err, vars.ErrRouteNotFound) {
log.Tracef("Adding for prefix %s: %v", prefix, err)
- // These errors are not critical but also we should not track and try to remove the routes either.
+ // These errors are not critical, but also we should not track and try to remove the routes either.
return nexthop, refcounter.ErrIgnore
}
return nexthop, err
@@ -135,6 +135,11 @@ func (r *SysOps) addRouteToNonVPNIntf(prefix netip.Prefix, vpnIntf *iface.WGIfac
return Nexthop{}, vars.ErrRouteNotAllowed
}
+ // Check if the prefix is part of any local subnets
+ if isLocal, subnet := r.isPrefixInLocalSubnets(prefix); isLocal {
+ return Nexthop{}, fmt.Errorf("prefix %s is part of local subnet %s: %w", prefix, subnet, vars.ErrRouteNotAllowed)
+ }
+
// Determine the exit interface and next hop for the prefix, so we can add a specific route
nexthop, err := GetNextHop(addr)
if err != nil {
@@ -167,6 +172,36 @@ func (r *SysOps) addRouteToNonVPNIntf(prefix netip.Prefix, vpnIntf *iface.WGIfac
return exitNextHop, nil
}
+func (r *SysOps) isPrefixInLocalSubnets(prefix netip.Prefix) (bool, *net.IPNet) {
+ localInterfaces, err := net.Interfaces()
+ if err != nil {
+ log.Errorf("Failed to get local interfaces: %v", err)
+ return false, nil
+ }
+
+ for _, intf := range localInterfaces {
+ addrs, err := intf.Addrs()
+ if err != nil {
+ log.Errorf("Failed to get addresses for interface %s: %v", intf.Name, err)
+ continue
+ }
+
+ for _, addr := range addrs {
+ ipnet, ok := addr.(*net.IPNet)
+ if !ok {
+ log.Errorf("Failed to convert address to IPNet: %v", addr)
+ continue
+ }
+
+ if ipnet.Contains(prefix.Addr().AsSlice()) {
+ return true, ipnet
+ }
+ }
+ }
+
+ return false, nil
+}
+
// genericAddVPNRoute adds a new route to the vpn interface, it splits the default prefix
// in two /1 prefixes to avoid replacing the existing default route
func (r *SysOps) genericAddVPNRoute(prefix netip.Prefix, intf *net.Interface) error {
diff --git a/client/internal/routemanager/systemops/systemops_windows_test.go b/client/internal/routemanager/systemops/systemops_windows_test.go
index 9180ed58c4a..19b00601775 100644
--- a/client/internal/routemanager/systemops/systemops_windows_test.go
+++ b/client/internal/routemanager/systemops/systemops_windows_test.go
@@ -73,7 +73,7 @@ var testCases = []testCase{
{
name: "To duplicate internal route without custom dialer via physical interface", // local route takes precedence
destination: "10.0.0.2:53",
- expectedSourceIP: "10.0.0.1",
+ expectedSourceIP: "127.0.0.1",
expectedDestPrefix: "10.0.0.0/8",
expectedNextHop: "0.0.0.0",
expectedInterface: "Loopback Pseudo-Interface 1",
@@ -110,7 +110,7 @@ var testCases = []testCase{
{
name: "To more specific route (local) without custom dialer via physical interface",
destination: "127.0.10.2:53",
- expectedSourceIP: "10.0.0.1",
+ expectedSourceIP: "127.0.0.1",
expectedDestPrefix: "127.0.0.0/8",
expectedNextHop: "0.0.0.0",
expectedInterface: "Loopback Pseudo-Interface 1",
@@ -181,31 +181,6 @@ func testRoute(t *testing.T, destination string, dialer dialer) *FindNetRouteOut
return combinedOutput
}
-func createAndSetupDummyInterface(t *testing.T, interfaceName, ipAddressCIDR string) string {
- t.Helper()
-
- ip, ipNet, err := net.ParseCIDR(ipAddressCIDR)
- require.NoError(t, err)
- subnetMaskSize, _ := ipNet.Mask.Size()
- script := fmt.Sprintf(`New-NetIPAddress -InterfaceAlias "%s" -IPAddress "%s" -PrefixLength %d -PolicyStore ActiveStore -Confirm:$False`, interfaceName, ip.String(), subnetMaskSize)
- _, err = exec.Command("powershell", "-Command", script).CombinedOutput()
- require.NoError(t, err, "Failed to assign IP address to loopback adapter")
-
- // Wait for the IP address to be applied
- ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
- defer cancel()
- err = waitForIPAddress(ctx, interfaceName, ip.String())
- require.NoError(t, err, "IP address not applied within timeout")
-
- t.Cleanup(func() {
- script = fmt.Sprintf(`Remove-NetIPAddress -InterfaceAlias "%s" -IPAddress "%s" -Confirm:$False`, interfaceName, ip.String())
- _, err = exec.Command("powershell", "-Command", script).CombinedOutput()
- require.NoError(t, err, "Failed to remove IP address from loopback adapter")
- })
-
- return interfaceName
-}
-
func fetchOriginalGateway() (*RouteInfo, error) {
cmd := exec.Command("powershell", "-Command", "Get-NetRoute -DestinationPrefix 0.0.0.0/0 | Select-Object Nexthop, RouteMetric, InterfaceAlias | ConvertTo-Json")
output, err := cmd.CombinedOutput()
@@ -231,30 +206,6 @@ func verifyOutput(t *testing.T, output *FindNetRouteOutput, sourceIP, destPrefix
assert.Equal(t, intf, output.InterfaceAlias, "Interface mismatch")
}
-func waitForIPAddress(ctx context.Context, interfaceAlias, expectedIPAddress string) error {
- ticker := time.NewTicker(1 * time.Second)
- defer ticker.Stop()
-
- for {
- select {
- case <-ctx.Done():
- return ctx.Err()
- case <-ticker.C:
- out, err := exec.Command("powershell", "-Command", fmt.Sprintf(`Get-NetIPAddress -InterfaceAlias "%s" | Select-Object -ExpandProperty IPAddress`, interfaceAlias)).CombinedOutput()
- if err != nil {
- return err
- }
-
- ipAddresses := strings.Split(strings.TrimSpace(string(out)), "\n")
- for _, ip := range ipAddresses {
- if strings.TrimSpace(ip) == expectedIPAddress {
- return nil
- }
- }
- }
- }
-}
-
func combineOutputs(outputs []FindNetRouteOutput) *FindNetRouteOutput {
var combined FindNetRouteOutput
@@ -285,5 +236,25 @@ func combineOutputs(outputs []FindNetRouteOutput) *FindNetRouteOutput {
func setupDummyInterfacesAndRoutes(t *testing.T) {
t.Helper()
- createAndSetupDummyInterface(t, "Loopback Pseudo-Interface 1", "10.0.0.1/8")
+ addDummyRoute(t, "10.0.0.0/8")
+}
+
+func addDummyRoute(t *testing.T, dstCIDR string) {
+ t.Helper()
+
+ script := fmt.Sprintf(`New-NetRoute -DestinationPrefix "%s" -InterfaceIndex 1 -PolicyStore ActiveStore`, dstCIDR)
+
+ output, err := exec.Command("powershell", "-Command", script).CombinedOutput()
+ if err != nil {
+ t.Logf("Failed to add dummy route: %v\nOutput: %s", err, output)
+ t.FailNow()
+ }
+
+ t.Cleanup(func() {
+ script = fmt.Sprintf(`Remove-NetRoute -DestinationPrefix "%s" -InterfaceIndex 1 -Confirm:$false`, dstCIDR)
+ output, err := exec.Command("powershell", "-Command", script).CombinedOutput()
+ if err != nil {
+ t.Logf("Failed to remove dummy route: %v\nOutput: %s", err, output)
+ }
+ })
}
From 19147f518ead57a89ac4e544ccbded54521d19dc Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Wed, 17 Jul 2024 23:48:37 +0200
Subject: [PATCH 23/77] Add faster availability DNS probe and update test
domain to .com (#2280)
* Add faster availability DNS probe and update test domain to .com
- Count success queries and compare it before doing after network map probes.
- Reduce the first dns probe to 500ms
- Updated test domain with com instead of . due to Palo alto DNS proxy server issues
* use fqdn
* Update client/internal/dns/upstream.go
Co-authored-by: Viktor Liu <17948409+lixmal@users.noreply.github.com>
---------
Co-authored-by: Viktor Liu <17948409+lixmal@users.noreply.github.com>
---
client/internal/dns/upstream.go | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/client/internal/dns/upstream.go b/client/internal/dns/upstream.go
index b502bf5eb1b..b3baf2fa8fd 100644
--- a/client/internal/dns/upstream.go
+++ b/client/internal/dns/upstream.go
@@ -24,7 +24,7 @@ const (
probeTimeout = 2 * time.Second
)
-const testRecord = "."
+const testRecord = "com."
type upstreamClient interface {
exchange(ctx context.Context, upstream string, r *dns.Msg) (*dns.Msg, time.Duration, error)
@@ -42,6 +42,7 @@ type upstreamResolverBase struct {
upstreamServers []string
disabled bool
failsCount atomic.Int32
+ successCount atomic.Int32
failsTillDeact int32
mutex sync.Mutex
reactivatePeriod time.Duration
@@ -124,6 +125,7 @@ func (u *upstreamResolverBase) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
return
}
+ u.successCount.Add(1)
log.Tracef("took %s to query the upstream %s", t, upstream)
err = w.WriteMsg(rm)
@@ -172,6 +174,11 @@ func (u *upstreamResolverBase) probeAvailability() {
default:
}
+ // avoid probe if upstreams could resolve at least one query and fails count is less than failsTillDeact
+ if u.successCount.Load() > 0 && u.failsCount.Load() < u.failsTillDeact {
+ return
+ }
+
var success bool
var mu sync.Mutex
var wg sync.WaitGroup
@@ -183,7 +190,7 @@ func (u *upstreamResolverBase) probeAvailability() {
wg.Add(1)
go func() {
defer wg.Done()
- err := u.testNameserver(upstream)
+ err := u.testNameserver(upstream, 500*time.Millisecond)
if err != nil {
errors = multierror.Append(errors, err)
log.Warnf("probing upstream nameserver %s: %s", upstream, err)
@@ -224,7 +231,7 @@ func (u *upstreamResolverBase) waitUntilResponse() {
}
for _, upstream := range u.upstreamServers {
- if err := u.testNameserver(upstream); err != nil {
+ if err := u.testNameserver(upstream, probeTimeout); err != nil {
log.Tracef("upstream check for %s: %s", upstream, err)
} else {
// at least one upstream server is available, stop probing
@@ -244,6 +251,7 @@ func (u *upstreamResolverBase) waitUntilResponse() {
log.Infof("upstreams %s are responsive again. Adding them back to system", u.upstreamServers)
u.failsCount.Store(0)
+ u.successCount.Add(1)
u.reactivate()
u.disabled = false
}
@@ -265,13 +273,14 @@ func (u *upstreamResolverBase) disable(err error) {
}
log.Warnf("Upstream resolving is Disabled for %v", reactivatePeriod)
+ u.successCount.Store(0)
u.deactivate(err)
u.disabled = true
go u.waitUntilResponse()
}
-func (u *upstreamResolverBase) testNameserver(server string) error {
- ctx, cancel := context.WithTimeout(u.ctx, probeTimeout)
+func (u *upstreamResolverBase) testNameserver(server string, timeout time.Duration) error {
+ ctx, cancel := context.WithTimeout(u.ctx, timeout)
defer cancel()
r := new(dns.Msg).SetQuestion(testRecord, dns.TypeSOA)
From 9a6de52dd09fa2edc9ef54cc48ea3acc84f56033 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Wed, 17 Jul 2024 23:49:09 +0200
Subject: [PATCH 24/77] Check if route interface is a Microsoft ISATAP device
(#2282)
check if the nexthop interfaces are Microsoft ISATAP devices and ignore their suffixes when comparing them
---
.../internal/networkmonitor/monitor_windows.go | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/client/internal/networkmonitor/monitor_windows.go b/client/internal/networkmonitor/monitor_windows.go
index e24bdd066d8..f58802e4e10 100644
--- a/client/internal/networkmonitor/monitor_windows.go
+++ b/client/internal/networkmonitor/monitor_windows.go
@@ -232,14 +232,20 @@ func stateFromInt(state uint8) string {
}
func compareIntf(a, b *net.Interface) int {
- if a == nil && b == nil {
+ switch {
+ case a == nil && b == nil:
return 0
- }
- if a == nil {
+ case a == nil:
return -1
- }
- if b == nil {
+ case b == nil:
return 1
+ case isIsatapInterface(a.Name) && isIsatapInterface(b.Name):
+ return 0
+ default:
+ return a.Index - b.Index
}
- return a.Index - b.Index
+}
+
+func isIsatapInterface(name string) bool {
+ return strings.HasPrefix(strings.ToLower(name), "isatap")
}
From c900fa81bbcaca684e54ea346c6a34b0ef6095b3 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Thu, 18 Jul 2024 12:15:14 +0200
Subject: [PATCH 25/77] Remove copy functions from signal (#2285)
remove migration function for wiretrustee directories to netbird
---
signal/cmd/run.go | 207 +++++++++++++++-------------------------------
1 file changed, 67 insertions(+), 140 deletions(-)
diff --git a/signal/cmd/run.go b/signal/cmd/run.go
index 8f75c1e04ab..cfc140acbdc 100644
--- a/signal/cmd/run.go
+++ b/signal/cmd/run.go
@@ -6,12 +6,8 @@ import (
"errors"
"flag"
"fmt"
- "io"
- "io/fs"
"net"
"net/http"
- "os"
- "path"
"strings"
"time"
@@ -58,9 +54,15 @@ var (
})
runCmd = &cobra.Command{
- Use: "run",
- Short: "start NetBird Signal Server daemon",
+ Use: "run",
+ Short: "start NetBird Signal Server daemon",
+ SilenceUsage: true,
PreRun: func(cmd *cobra.Command, args []string) {
+ err := util.InitLog(logLevel, logFile)
+ if err != nil {
+ log.Fatalf("failed initializing log %v", err)
+ }
+
flag.Parse()
// detect whether user specified a port
@@ -83,40 +85,9 @@ var (
RunE: func(cmd *cobra.Command, args []string) error {
flag.Parse()
- err := util.InitLog(logLevel, logFile)
+ opts, certManager, err := getTLSConfigurations()
if err != nil {
- log.Fatalf("failed initializing log %v", err)
- }
-
- if signalSSLDir == "" {
- oldPath := "/var/lib/wiretrustee"
- if migrateToNetbird(oldPath, defaultSignalSSLDir) {
- if err := cpDir(oldPath, defaultSignalSSLDir); err != nil {
- log.Fatal(err)
- }
- }
- }
-
- var opts []grpc.ServerOption
- var certManager *autocert.Manager
- var tlsConfig *tls.Config
- if signalLetsencryptDomain != "" {
- certManager, err = encryption.CreateCertManager(signalSSLDir, signalLetsencryptDomain)
- if err != nil {
- return err
- }
- transportCredentials := credentials.NewTLS(certManager.TLSConfig())
- opts = append(opts, grpc.Creds(transportCredentials))
- log.Infof("setting up TLS with LetsEncrypt.")
- } else if signalCertFile != "" && signalCertKey != "" {
- tlsConfig, err = loadTLSConfig(signalCertFile, signalCertKey)
- if err != nil {
- log.Errorf("cannot load TLS credentials: %v", err)
- return err
- }
- transportCredentials := credentials.NewTLS(tlsConfig)
- opts = append(opts, grpc.Creds(transportCredentials))
- log.Infof("setting up TLS with custom certificates.")
+ return err
}
metricsServer := metrics.NewServer(metricsPort, "")
@@ -141,24 +112,15 @@ var (
proto.RegisterSignalExchangeServer(grpcServer, srv)
grpcRootHandler := grpcHandlerFunc(grpcServer)
- var compatListener net.Listener
- var grpcListener net.Listener
- var httpListener net.Listener
if certManager != nil {
- // a call to certManager.Listener() always creates a new listener so we do it once
- httpListener := certManager.Listener()
- if signalPort == 443 {
- // running gRPC and HTTP cert manager on the same port
- serveHTTP(httpListener, certManager.HTTPHandler(grpcRootHandler))
- log.Infof("running HTTP server (LetsEncrypt challenge handler) and gRPC server on the same port: %s", httpListener.Addr().String())
- } else {
- // Start the HTTP cert manager server separately
- serveHTTP(httpListener, certManager.HTTPHandler(nil))
- log.Infof("running HTTP server (LetsEncrypt challenge handler): %s", httpListener.Addr().String())
- }
+ startServerWithCertManager(certManager, grpcRootHandler)
}
+ var compatListener net.Listener
+ var grpcListener net.Listener
+ var httpListener net.Listener
+
// If certManager is configured and signalPort == 443, then the gRPC server has already been started
if certManager == nil || signalPort != 443 {
grpcListener, err = serveGRPC(grpcServer, signalPort)
@@ -211,6 +173,58 @@ var (
}
)
+func getTLSConfigurations() ([]grpc.ServerOption, *autocert.Manager, error) {
+ var (
+ err error
+ certManager *autocert.Manager
+ tlsConfig *tls.Config
+ )
+
+ if signalLetsencryptDomain == "" && signalCertFile == "" && signalCertKey == "" {
+ log.Infof("running without TLS")
+ return nil, nil, nil
+ }
+
+ if signalLetsencryptDomain != "" {
+ certManager, err = encryption.CreateCertManager(signalSSLDir, signalLetsencryptDomain)
+ if err != nil {
+ return nil, certManager, err
+ }
+ tlsConfig = certManager.TLSConfig()
+ log.Infof("setting up TLS with LetsEncrypt.")
+ } else {
+ if signalCertFile == "" || signalCertKey == "" {
+ log.Errorf("both cert-file and cert-key must be provided when not using LetsEncrypt")
+ return nil, certManager, errors.New("both cert-file and cert-key must be provided when not using LetsEncrypt")
+ }
+
+ tlsConfig, err = loadTLSConfig(signalCertFile, signalCertKey)
+ if err != nil {
+ log.Errorf("cannot load TLS credentials: %v", err)
+ return nil, certManager, err
+ }
+ log.Infof("setting up TLS with custom certificates.")
+ }
+
+ transportCredentials := credentials.NewTLS(tlsConfig)
+
+ return []grpc.ServerOption{grpc.Creds(transportCredentials)}, certManager, err
+}
+
+func startServerWithCertManager(certManager *autocert.Manager, grpcRootHandler http.Handler) {
+ // a call to certManager.Listener() always creates a new listener so we do it once
+ httpListener := certManager.Listener()
+ if signalPort == 443 {
+ // running gRPC and HTTP cert manager on the same port
+ serveHTTP(httpListener, certManager.HTTPHandler(grpcRootHandler))
+ log.Infof("running HTTP server (LetsEncrypt challenge handler) and gRPC server on the same port: %s", httpListener.Addr().String())
+ } else {
+ // Start the HTTP cert manager server separately
+ serveHTTP(httpListener, certManager.HTTPHandler(nil))
+ log.Infof("running HTTP server (LetsEncrypt challenge handler): %s", httpListener.Addr().String())
+ }
+}
+
func grpcHandlerFunc(grpcServer *grpc.Server) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
grpcHeader := strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") ||
@@ -272,93 +286,6 @@ func loadTLSConfig(certFile string, certKey string) (*tls.Config, error) {
return config, nil
}
-func cpFile(src, dst string) error {
- var err error
- var srcfd *os.File
- var dstfd *os.File
- var srcinfo os.FileInfo
-
- if srcfd, err = os.Open(src); err != nil {
- return err
- }
- defer srcfd.Close()
-
- if dstfd, err = os.Create(dst); err != nil {
- return err
- }
- defer dstfd.Close()
-
- if _, err = io.Copy(dstfd, srcfd); err != nil {
- return err
- }
- if srcinfo, err = os.Stat(src); err != nil {
- return err
- }
- return os.Chmod(dst, srcinfo.Mode())
-}
-
-func copySymLink(source, dest string) error {
- link, err := os.Readlink(source)
- if err != nil {
- return err
- }
- return os.Symlink(link, dest)
-}
-
-func cpDir(src string, dst string) error {
- var err error
- var fds []os.DirEntry
- var srcinfo os.FileInfo
-
- if srcinfo, err = os.Stat(src); err != nil {
- return err
- }
-
- if err = os.MkdirAll(dst, srcinfo.Mode()); err != nil {
- return err
- }
-
- if fds, err = os.ReadDir(src); err != nil {
- return err
- }
- for _, fd := range fds {
- srcfp := path.Join(src, fd.Name())
- dstfp := path.Join(dst, fd.Name())
-
- fileInfo, err := os.Stat(srcfp)
- if err != nil {
- log.Fatalf("Couldn't get fileInfo; %v", err)
- }
-
- switch fileInfo.Mode() & os.ModeType {
- case os.ModeSymlink:
- if err = copySymLink(srcfp, dstfp); err != nil {
- log.Fatalf("Failed to copy from %s to %s; %v", srcfp, dstfp, err)
- }
- case os.ModeDir:
- if err = cpDir(srcfp, dstfp); err != nil {
- log.Fatalf("Failed to copy from %s to %s; %v", srcfp, dstfp, err)
- }
- default:
- if err = cpFile(srcfp, dstfp); err != nil {
- log.Fatalf("Failed to copy from %s to %s; %v", srcfp, dstfp, err)
- }
- }
- }
- return nil
-}
-
-func migrateToNetbird(oldPath, newPath string) bool {
- _, errOld := os.Stat(oldPath)
- _, errNew := os.Stat(newPath)
-
- if errors.Is(errOld, fs.ErrNotExist) || errNew == nil {
- return false
- }
-
- return true
-}
-
func init() {
runCmd.PersistentFlags().IntVar(&signalPort, "port", 80, "Server port to listen on (defaults to 443 if TLS is enabled, 80 otherwise")
runCmd.Flags().StringVar(&signalSSLDir, "ssl-dir", defaultSignalSSLDir, "server ssl directory location. *Required only for Let's Encrypt certificates.")
From ef1a39cb01d1231d76e503870360479f7addb957 Mon Sep 17 00:00:00 2001
From: Carlos Hernandez
Date: Thu, 18 Jul 2024 08:39:41 -0600
Subject: [PATCH 26/77] Refactor macOS system DNS configuration (#2284)
On macOS use the recommended settings for providing split DNS. As per
the docs an empty string will force the configuration to be the default.
In order to to support split DNS an additional service config is added
for the local server and search domain settings.
see: https://developer.apple.com/documentation/devicemanagement/vpn/dns
---
client/internal/dns/host.go | 6 ++
client/internal/dns/host_darwin.go | 161 +++++++++++++++++++----------
2 files changed, 115 insertions(+), 52 deletions(-)
diff --git a/client/internal/dns/host.go b/client/internal/dns/host.go
index 3070763a6ef..e55a0705556 100644
--- a/client/internal/dns/host.go
+++ b/client/internal/dns/host.go
@@ -15,6 +15,12 @@ type hostManager interface {
restoreUncleanShutdownDNS(storedDNSAddress *netip.Addr) error
}
+type SystemDNSSettings struct {
+ Domains []string
+ ServerIP string
+ ServerPort int
+}
+
type HostDNSConfig struct {
Domains []DomainConfig `json:"domains"`
RouteAll bool `json:"routeAll"`
diff --git a/client/internal/dns/host_darwin.go b/client/internal/dns/host_darwin.go
index 5ae84fb9116..70d2443ef7e 100644
--- a/client/internal/dns/host_darwin.go
+++ b/client/internal/dns/host_darwin.go
@@ -7,6 +7,7 @@ import (
"bytes"
"fmt"
"io"
+ "net"
"net/netip"
"os/exec"
"strconv"
@@ -18,7 +19,7 @@ import (
const (
netbirdDNSStateKeyFormat = "State:/Network/Service/NetBird-%s/DNS"
globalIPv4State = "State:/Network/Global/IPv4"
- primaryServiceSetupKeyFormat = "Setup:/Network/Service/%s/DNS"
+ primaryServiceStateKeyFormat = "State:/Network/Service/%s/DNS"
keySupplementalMatchDomains = "SupplementalMatchDomains"
keySupplementalMatchDomainsNoSearch = "SupplementalMatchDomainsNoSearch"
keyServerAddresses = "ServerAddresses"
@@ -28,12 +29,12 @@ const (
scutilPath = "/usr/sbin/scutil"
searchSuffix = "Search"
matchSuffix = "Match"
+ localSuffix = "Local"
)
type systemConfigurator struct {
- // primaryServiceID primary interface in the system. AKA the interface with the default route
- primaryServiceID string
- createdKeys map[string]struct{}
+ createdKeys map[string]struct{}
+ systemDNSSettings SystemDNSSettings
}
func newHostManager() (hostManager, error) {
@@ -49,20 +50,6 @@ func (s *systemConfigurator) supportCustomPort() bool {
func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig) error {
var err error
- if config.RouteAll {
- err = s.addDNSSetupForAll(config.ServerIP, config.ServerPort)
- if err != nil {
- return fmt.Errorf("add dns setup for all: %w", err)
- }
- } else if s.primaryServiceID != "" {
- err = s.removeKeyFromSystemConfig(getKeyWithInput(primaryServiceSetupKeyFormat, s.primaryServiceID))
- if err != nil {
- return fmt.Errorf("remote key from system config: %w", err)
- }
- s.primaryServiceID = ""
- log.Infof("removed %s:%d as main DNS resolver for this peer", config.ServerIP, config.ServerPort)
- }
-
// create a file for unclean shutdown detection
if err := createUncleanShutdownIndicator(); err != nil {
log.Errorf("failed to create unclean shutdown file: %s", err)
@@ -73,6 +60,19 @@ func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig) error {
matchDomains []string
)
+ err = s.recordSystemDNSSettings(true)
+ if err != nil {
+ log.Errorf("unable to update record of System's DNS config: %s", err.Error())
+ }
+
+ if config.RouteAll {
+ searchDomains = append(searchDomains, "\"\"")
+ err = s.addLocalDNS()
+ if err != nil {
+ log.Infof("failed to enable split DNS")
+ }
+ }
+
for _, dConf := range config.Domains {
if dConf.Disabled {
continue
@@ -119,10 +119,6 @@ func (s *systemConfigurator) restoreHostDNS() error {
}
log.Infof("removing %s domains from system", keyType)
}
- if s.primaryServiceID != "" {
- lines += buildRemoveKeyOperation(getKeyWithInput(primaryServiceSetupKeyFormat, s.primaryServiceID))
- log.Infof("restoring DNS resolver configuration for system")
- }
_, err := runSystemConfigCommand(wrapCommand(lines))
if err != nil {
log.Errorf("got an error while cleaning the system configuration: %s", err)
@@ -148,6 +144,97 @@ func (s *systemConfigurator) removeKeyFromSystemConfig(key string) error {
return nil
}
+func (s *systemConfigurator) addLocalDNS() error {
+ if s.systemDNSSettings.ServerIP == "" || len(s.systemDNSSettings.Domains) == 0 {
+ err := s.recordSystemDNSSettings(true)
+ log.Errorf("Unable to get system DNS configuration")
+ return err
+ }
+ localKey := getKeyWithInput(netbirdDNSStateKeyFormat, localSuffix)
+ if s.systemDNSSettings.ServerIP != "" && len(s.systemDNSSettings.Domains) != 0 {
+ err := s.addSearchDomains(localKey, strings.Join(s.systemDNSSettings.Domains, " "), s.systemDNSSettings.ServerIP, s.systemDNSSettings.ServerPort)
+ if err != nil {
+ return fmt.Errorf("couldn't add local network DNS conf: %w", err)
+ }
+ } else {
+ log.Info("Not enabling local DNS server")
+ }
+
+ return nil
+}
+
+func (s *systemConfigurator) recordSystemDNSSettings(force bool) error {
+ if s.systemDNSSettings.ServerIP != "" && len(s.systemDNSSettings.Domains) != 0 && !force {
+ return nil
+ }
+
+ systemDNSSettings, err := s.getSystemDNSSettings()
+ if err != nil {
+ return fmt.Errorf("couldn't get current DNS config: %w", err)
+ }
+ s.systemDNSSettings = systemDNSSettings
+
+ return nil
+}
+
+func (s *systemConfigurator) getSystemDNSSettings() (SystemDNSSettings, error) {
+ primaryServiceKey, _, err := s.getPrimaryService()
+ if err != nil || primaryServiceKey == "" {
+ return SystemDNSSettings{}, fmt.Errorf("couldn't find the primary service key: %w", err)
+ }
+ dnsServiceKey := getKeyWithInput(primaryServiceStateKeyFormat, primaryServiceKey)
+ line := buildCommandLine("show", dnsServiceKey, "")
+ stdinCommands := wrapCommand(line)
+
+ b, err := runSystemConfigCommand(stdinCommands)
+ if err != nil {
+ return SystemDNSSettings{}, fmt.Errorf("sending the command: %w", err)
+ }
+
+ var dnsSettings SystemDNSSettings
+ inSearchDomainsArray := false
+ inServerAddressesArray := false
+
+ scanner := bufio.NewScanner(bytes.NewReader(b))
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+ switch {
+ case strings.HasPrefix(line, "DomainName :"):
+ domainName := strings.TrimSpace(strings.Split(line, ":")[1])
+ dnsSettings.Domains = append(dnsSettings.Domains, domainName)
+ case line == "SearchDomains : {":
+ inSearchDomainsArray = true
+ continue
+ case line == "ServerAddresses : {":
+ inServerAddressesArray = true
+ continue
+ case line == "}":
+ inSearchDomainsArray = false
+ inServerAddressesArray = false
+ }
+
+ if inSearchDomainsArray {
+ searchDomains := strings.Split(line, " : ")
+ dnsSettings.Domains = append(dnsSettings.Domains, searchDomains...)
+ } else if inServerAddressesArray {
+ address := strings.Split(line, " : ")[1]
+ if ip := net.ParseIP(address); ip != nil && ip.To4() != nil {
+ dnsSettings.ServerIP = address
+ inServerAddressesArray = false // Stop reading after finding the first IPv4 address
+ }
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return dnsSettings, err
+ }
+
+ // default to 53 port
+ dnsSettings.ServerPort = 53
+
+ return dnsSettings, nil
+}
+
func (s *systemConfigurator) addSearchDomains(key, domains string, ip string, port int) error {
err := s.addDNSState(key, domains, ip, port, true)
if err != nil {
@@ -194,23 +281,6 @@ func (s *systemConfigurator) addDNSState(state, domains, dnsServer string, port
return nil
}
-func (s *systemConfigurator) addDNSSetupForAll(dnsServer string, port int) error {
- primaryServiceKey, existingNameserver, err := s.getPrimaryService()
- if err != nil || primaryServiceKey == "" {
- return fmt.Errorf("couldn't find the primary service key: %w", err)
- }
-
- err = s.addDNSSetup(getKeyWithInput(primaryServiceSetupKeyFormat, primaryServiceKey), dnsServer, port, existingNameserver)
- if err != nil {
- return fmt.Errorf("add dns setup: %w", err)
- }
-
- log.Infof("configured %s:%d as main DNS resolver for this peer", dnsServer, port)
- s.primaryServiceID = primaryServiceKey
-
- return nil
-}
-
func (s *systemConfigurator) getPrimaryService() (string, string, error) {
line := buildCommandLine("show", globalIPv4State, "")
stdinCommands := wrapCommand(line)
@@ -239,19 +309,6 @@ func (s *systemConfigurator) getPrimaryService() (string, string, error) {
return primaryService, router, nil
}
-func (s *systemConfigurator) addDNSSetup(setupKey, dnsServer string, port int, existingDNSServer string) error {
- lines := buildAddCommandLine(keySupplementalMatchDomainsNoSearch, digitSymbol+strconv.Itoa(0))
- lines += buildAddCommandLine(keyServerAddresses, arraySymbol+dnsServer+" "+existingDNSServer)
- lines += buildAddCommandLine(keyServerPort, digitSymbol+strconv.Itoa(port))
- addDomainCommand := buildCreateStateWithOperation(setupKey, lines)
- stdinCommands := wrapCommand(addDomainCommand)
- _, err := runSystemConfigCommand(stdinCommands)
- if err != nil {
- return fmt.Errorf("applying dns setup, error: %w", err)
- }
- return nil
-}
-
func (s *systemConfigurator) restoreUncleanShutdownDNS(*netip.Addr) error {
if err := s.restoreHostDNS(); err != nil {
return fmt.Errorf("restoring dns via scutil: %w", err)
From c815ad86fd7ccf2804995f8c956540f76c188535 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Thu, 18 Jul 2024 18:06:09 +0200
Subject: [PATCH 27/77] Fix macOS DNS unclean shutdown restore call on startup
(#2286)
previously, we called the restore method from the startup when there was an unclean shutdown. But it never had the state keys to clean since they are stored in memory
this change addresses the issue by falling back to default values when restoring the host's DNS
---
client/internal/dns/host_darwin.go | 27 +++++++++++++++++++--------
1 file changed, 19 insertions(+), 8 deletions(-)
diff --git a/client/internal/dns/host_darwin.go b/client/internal/dns/host_darwin.go
index 70d2443ef7e..b5f6f9295cc 100644
--- a/client/internal/dns/host_darwin.go
+++ b/client/internal/dns/host_darwin.go
@@ -110,19 +110,17 @@ func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig) error {
}
func (s *systemConfigurator) restoreHostDNS() error {
- lines := ""
- for key := range s.createdKeys {
- lines += buildRemoveKeyOperation(key)
+ keys := s.getRemovableKeysWithDefaults()
+ for _, key := range keys {
keyType := "search"
if strings.Contains(key, matchSuffix) {
keyType = "match"
}
log.Infof("removing %s domains from system", keyType)
- }
- _, err := runSystemConfigCommand(wrapCommand(lines))
- if err != nil {
- log.Errorf("got an error while cleaning the system configuration: %s", err)
- return fmt.Errorf("clean system: %w", err)
+ err := s.removeKeyFromSystemConfig(key)
+ if err != nil {
+ log.Errorf("failed to remove %s domains from system: %s", keyType, err)
+ }
}
if err := removeUncleanShutdownIndicator(); err != nil {
@@ -132,6 +130,19 @@ func (s *systemConfigurator) restoreHostDNS() error {
return nil
}
+func (s *systemConfigurator) getRemovableKeysWithDefaults() []string {
+ if len(s.createdKeys) == 0 {
+ // return defaults for startup calls
+ return []string{getKeyWithInput(netbirdDNSStateKeyFormat, searchSuffix), getKeyWithInput(netbirdDNSStateKeyFormat, matchSuffix)}
+ }
+
+ keys := make([]string, 0, len(s.createdKeys))
+ for key := range s.createdKeys {
+ keys = append(keys, key)
+ }
+ return keys
+}
+
func (s *systemConfigurator) removeKeyFromSystemConfig(key string) error {
line := buildRemoveKeyOperation(key)
_, err := runSystemConfigCommand(wrapCommand(line))
From 0a8c78deb142bf69544bc3206b7721f23f6cd69c Mon Sep 17 00:00:00 2001
From: Carlos Hernandez
Date: Fri, 19 Jul 2024 08:44:12 -0600
Subject: [PATCH 28/77] Minor fix local dns search domain (#2287)
---
client/internal/dns/host_darwin.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/client/internal/dns/host_darwin.go b/client/internal/dns/host_darwin.go
index b5f6f9295cc..5dee305c2ed 100644
--- a/client/internal/dns/host_darwin.go
+++ b/client/internal/dns/host_darwin.go
@@ -225,8 +225,8 @@ func (s *systemConfigurator) getSystemDNSSettings() (SystemDNSSettings, error) {
}
if inSearchDomainsArray {
- searchDomains := strings.Split(line, " : ")
- dnsSettings.Domains = append(dnsSettings.Domains, searchDomains...)
+ searchDomain := strings.Split(line, " : ")[1]
+ dnsSettings.Domains = append(dnsSettings.Domains, searchDomain)
} else if inServerAddressesArray {
address := strings.Split(line, " : ")[1]
if ip := net.ParseIP(address); ip != nil && ip.To4() != nil {
From 926e11b086b9b2753d0b28dee888af17c3026638 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Mon, 22 Jul 2024 15:35:17 +0200
Subject: [PATCH 29/77] Remove default allow for UDP on unmatched packet
(#2300)
This fixes an issue where UDP rules were ineffective for userspace clients (Windows/macOS)
---
client/firewall/uspfilter/uspfilter.go | 1 -
1 file changed, 1 deletion(-)
diff --git a/client/firewall/uspfilter/uspfilter.go b/client/firewall/uspfilter/uspfilter.go
index 427a738254a..75792e9c06b 100644
--- a/client/firewall/uspfilter/uspfilter.go
+++ b/client/firewall/uspfilter/uspfilter.go
@@ -337,7 +337,6 @@ func validateRule(ip net.IP, packetData []byte, rules map[string]Rule, d *decode
if rule.dPort != 0 && rule.dPort == uint16(d.udp.DstPort) {
return rule.drop, true
}
- return rule.drop, true
case layers.LayerTypeICMPv4, layers.LayerTypeICMPv6:
return rule.drop, true
}
From 788f1309415fbd819656f1355f11f1a8dd53919c Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Mon, 22 Jul 2024 15:49:25 +0200
Subject: [PATCH 30/77] Retry management connection only on context canceled
(#2301)
---
management/client/grpc.go | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/management/client/grpc.go b/management/client/grpc.go
index 568c1531339..eaadcd31736 100644
--- a/management/client/grpc.go
+++ b/management/client/grpc.go
@@ -334,8 +334,11 @@ func (c *GrpcClient) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*pro
Body: loginReq,
})
if err != nil {
- log.Printf("Login error: %v", err)
- return err
+ // retry only on context canceled
+ if s, ok := gstatus.FromError(err); ok && s.Code() == codes.Canceled {
+ return err
+ }
+ return backoff.Permanent(err)
}
return nil
From 268e801ec565e1dd3d4002f725bd5c6b5fc765d6 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Mon, 22 Jul 2024 19:44:15 +0200
Subject: [PATCH 31/77] Ignore network monitor checks for software interfaces
(#2302)
ignore checks for Teredo and ISATAP interfaces
---
client/internal/networkmonitor/monitor_windows.go | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/client/internal/networkmonitor/monitor_windows.go b/client/internal/networkmonitor/monitor_windows.go
index f58802e4e10..308b2aa4586 100644
--- a/client/internal/networkmonitor/monitor_windows.go
+++ b/client/internal/networkmonitor/monitor_windows.go
@@ -99,6 +99,11 @@ func routeChanged(nexthop systemops.Nexthop, intf *net.Interface, routes []syste
return false
}
+ if isSoftInterface(nexthop.Intf.Name) {
+ log.Tracef("network monitor: ignoring default route change for soft interface %s", nexthop.Intf.Name)
+ return false
+ }
+
unspec := getUnspecifiedPrefix(nexthop.IP)
defaultRoutes, foundMatchingRoute := processRoutes(nexthop, intf, routes, unspec)
@@ -119,7 +124,7 @@ func getUnspecifiedPrefix(ip netip.Addr) netip.Prefix {
return netip.PrefixFrom(netip.IPv4Unspecified(), 0)
}
-func processRoutes(nexthop systemops.Nexthop, intf *net.Interface, routes []systemops.Route, unspec netip.Prefix) ([]string, bool) {
+func processRoutes(nexthop systemops.Nexthop, nexthopIntf *net.Interface, routes []systemops.Route, unspec netip.Prefix) ([]string, bool) {
var defaultRoutes []string
foundMatchingRoute := false
@@ -128,7 +133,7 @@ func processRoutes(nexthop systemops.Nexthop, intf *net.Interface, routes []syst
routeInfo := formatRouteInfo(r)
defaultRoutes = append(defaultRoutes, routeInfo)
- if r.Nexthop == nexthop.IP && compareIntf(r.Interface, intf) == 0 {
+ if r.Nexthop == nexthop.IP && compareIntf(r.Interface, nexthopIntf) == 0 {
foundMatchingRoute = true
log.Debugf("network monitor: found matching default route: %s", routeInfo)
}
@@ -239,13 +244,11 @@ func compareIntf(a, b *net.Interface) int {
return -1
case b == nil:
return 1
- case isIsatapInterface(a.Name) && isIsatapInterface(b.Name):
- return 0
default:
return a.Index - b.Index
}
}
-func isIsatapInterface(name string) bool {
- return strings.HasPrefix(strings.ToLower(name), "isatap")
+func isSoftInterface(name string) bool {
+ return strings.Contains(strings.ToLower(name), "isatap") || strings.Contains(strings.ToLower(name), "teredo")
}
From 63aeeb834dc9a7834f9aab2ff29bba11b060b9c7 Mon Sep 17 00:00:00 2001
From: Zoltan Papp
Date: Wed, 24 Jul 2024 13:27:01 +0200
Subject: [PATCH 32/77] Fix error handling (#2316)
---
signal/cmd/run.go | 2 +-
signal/metrics/metrics.go | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/signal/cmd/run.go b/signal/cmd/run.go
index cfc140acbdc..61f7a32a705 100644
--- a/signal/cmd/run.go
+++ b/signal/cmd/run.go
@@ -90,7 +90,7 @@ var (
return err
}
- metricsServer := metrics.NewServer(metricsPort, "")
+ metricsServer, err := metrics.NewServer(metricsPort, "")
if err != nil {
return fmt.Errorf("setup metrics: %v", err)
}
diff --git a/signal/metrics/metrics.go b/signal/metrics/metrics.go
index 30db1600afe..f411501cbca 100644
--- a/signal/metrics/metrics.go
+++ b/signal/metrics/metrics.go
@@ -26,10 +26,10 @@ type Metrics struct {
}
// NewServer initializes and returns a new Metrics instance
-func NewServer(port int, endpoint string) *Metrics {
+func NewServer(port int, endpoint string) (*Metrics, error) {
exporter, err := prometheus.New()
if err != nil {
- return nil
+ return nil, err
}
provider := metric.NewMeterProvider(metric.WithReader(exporter))
@@ -57,7 +57,7 @@ func NewServer(port int, endpoint string) *Metrics {
provider: provider,
Endpoint: endpoint,
Server: server,
- }
+ }, nil
}
// Shutdown stops the metrics server
From 45fd1e9c21dc520bf66c83bb4d52ba4ccbe656c9 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Thu, 25 Jul 2024 16:22:04 +0200
Subject: [PATCH 33/77] add save peer status test for connected peers (#2321)
---
management/server/sql_store_test.go | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/management/server/sql_store_test.go b/management/server/sql_store_test.go
index f46ca7e5db1..7f48810d7eb 100644
--- a/management/server/sql_store_test.go
+++ b/management/server/sql_store_test.go
@@ -402,6 +402,17 @@ func TestSqlite_SavePeerStatus(t *testing.T) {
actual := account.Peers["testpeer"].Status
assert.Equal(t, newStatus, *actual)
+
+ newStatus.Connected = true
+
+ err = store.SavePeerStatus(account.Id, "testpeer", newStatus)
+ require.NoError(t, err)
+
+ account, err = store.GetAccount(context.Background(), account.Id)
+ require.NoError(t, err)
+
+ actual = account.Peers["testpeer"].Status
+ assert.Equal(t, newStatus, *actual)
}
func TestSqlite_SavePeerLocation(t *testing.T) {
if runtime.GOOS == "windows" {
From 1f48fdf6cadca8576bca578dca829e9672cb98ce Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Fri, 26 Jul 2024 07:49:05 +0200
Subject: [PATCH 34/77] Add SavePeer method to prevent a possible account
inconsistency (#2296)
SyncPeer was storing the account with a simple read lock
This change introduces the SavePeer method to the store to be used in these cases
---
management/server/file_store.go | 20 ++++++++++++
management/server/peer.go | 5 +--
management/server/sql_store.go | 48 +++++++++++++++++++++++-----
management/server/sql_store_test.go | 49 +++++++++++++++++++++++++++++
management/server/store.go | 4 ++-
5 files changed, 116 insertions(+), 10 deletions(-)
diff --git a/management/server/file_store.go b/management/server/file_store.go
index c649602e2a2..9a146283279 100644
--- a/management/server/file_store.go
+++ b/management/server/file_store.go
@@ -666,6 +666,26 @@ func (s *FileStore) SaveInstallationID(ctx context.Context, ID string) error {
return s.persist(ctx, s.storeFile)
}
+// SavePeer saves the peer in the account
+func (s *FileStore) SavePeer(_ context.Context, accountID string, peer *nbpeer.Peer) error {
+ s.mux.Lock()
+ defer s.mux.Unlock()
+
+ account, err := s.getAccount(accountID)
+ if err != nil {
+ return err
+ }
+
+ newPeer := peer.Copy()
+
+ account.Peers[peer.ID] = newPeer
+
+ s.PeerKeyID2AccountID[peer.Key] = accountID
+ s.PeerID2AccountID[peer.ID] = accountID
+
+ return nil
+}
+
// SavePeerStatus stores the PeerStatus in memory. It doesn't attempt to persist data to speed up things.
// PeerStatus will be saved eventually when some other changes occur.
func (s *FileStore) SavePeerStatus(accountID, peerID string, peerStatus nbpeer.PeerStatus) error {
diff --git a/management/server/peer.go b/management/server/peer.go
index b8605fbb741..ec8b773b065 100644
--- a/management/server/peer.go
+++ b/management/server/peer.go
@@ -7,10 +7,11 @@ import (
"strings"
"time"
- "github.com/netbirdio/netbird/management/server/posture"
"github.com/rs/xid"
log "github.com/sirupsen/logrus"
+ "github.com/netbirdio/netbird/management/server/posture"
+
"github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server/activity"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
@@ -539,7 +540,7 @@ func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync PeerSync, ac
peer, updated := updatePeerMeta(peer, sync.Meta, account)
if updated {
- err = am.Store.SaveAccount(ctx, account)
+ err = am.Store.SavePeer(ctx, account.Id, peer)
if err != nil {
return nil, nil, nil, err
}
diff --git a/management/server/sql_store.go b/management/server/sql_store.go
index 37cc10d8bb4..7648538c3a4 100644
--- a/management/server/sql_store.go
+++ b/management/server/sql_store.go
@@ -31,8 +31,10 @@ import (
)
const (
- storeSqliteFileName = "store.db"
- idQueryCondition = "id = ?"
+ storeSqliteFileName = "store.db"
+ idQueryCondition = "id = ?"
+ accountAndIDQueryCondition = "account_id = ? and id = ?"
+ peerNotFoundFMT = "peer %s not found"
)
// SqlStore represents an account storage backed by a Sql DB persisted to disk
@@ -271,6 +273,38 @@ func (s *SqlStore) GetInstallationID() string {
return installation.InstallationIDValue
}
+func (s *SqlStore) SavePeer(ctx context.Context, accountID string, peer *nbpeer.Peer) error {
+ // To maintain data integrity, we create a copy of the peer's to prevent unintended updates to other fields.
+ peerCopy := peer.Copy()
+ peerCopy.AccountID = accountID
+
+ err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
+ // check if peer exists before saving
+ var peerID string
+ result := tx.Model(&nbpeer.Peer{}).Select("id").Find(&peerID, accountAndIDQueryCondition, accountID, peer.ID)
+ if result.Error != nil {
+ return result.Error
+ }
+
+ if peerID == "" {
+ return status.Errorf(status.NotFound, peerNotFoundFMT, peer.ID)
+ }
+
+ result = tx.Model(&nbpeer.Peer{}).Where(accountAndIDQueryCondition, accountID, peer.ID).Save(peerCopy)
+ if result.Error != nil {
+ return result.Error
+ }
+
+ return nil
+ })
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
func (s *SqlStore) SavePeerStatus(accountID, peerID string, peerStatus nbpeer.PeerStatus) error {
var peerCopy nbpeer.Peer
peerCopy.Status = &peerStatus
@@ -281,14 +315,14 @@ func (s *SqlStore) SavePeerStatus(accountID, peerID string, peerStatus nbpeer.Pe
}
result := s.db.Model(&nbpeer.Peer{}).
Select(fieldsToUpdate).
- Where("account_id = ? AND id = ?", accountID, peerID).
+ Where(accountAndIDQueryCondition, accountID, peerID).
Updates(&peerCopy)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
- return status.Errorf(status.NotFound, "peer %s not found", peerID)
+ return status.Errorf(status.NotFound, peerNotFoundFMT, peerID)
}
return nil
@@ -302,7 +336,7 @@ func (s *SqlStore) SavePeerLocation(accountID string, peerWithLocation *nbpeer.P
peerCopy.Location = peerWithLocation.Location
result := s.db.Model(&nbpeer.Peer{}).
- Where("account_id = ? and id = ?", accountID, peerWithLocation.ID).
+ Where(accountAndIDQueryCondition, accountID, peerWithLocation.ID).
Updates(peerCopy)
if result.Error != nil {
@@ -310,7 +344,7 @@ func (s *SqlStore) SavePeerLocation(accountID string, peerWithLocation *nbpeer.P
}
if result.RowsAffected == 0 {
- return status.Errorf(status.NotFound, "peer %s not found", peerWithLocation.ID)
+ return status.Errorf(status.NotFound, peerNotFoundFMT, peerWithLocation.ID)
}
return nil
@@ -644,7 +678,7 @@ func (s *SqlStore) GetAccountSettings(ctx context.Context, accountID string) (*S
func (s *SqlStore) SaveUserLastLogin(accountID, userID string, lastLogin time.Time) error {
var user User
- result := s.db.First(&user, "account_id = ? and id = ?", accountID, userID)
+ result := s.db.First(&user, accountAndIDQueryCondition, accountID, userID)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return status.Errorf(status.NotFound, "user %s not found", userID)
diff --git a/management/server/sql_store_test.go b/management/server/sql_store_test.go
index 7f48810d7eb..ce4ee531a11 100644
--- a/management/server/sql_store_test.go
+++ b/management/server/sql_store_test.go
@@ -362,6 +362,54 @@ func TestSqlite_GetAccount(t *testing.T) {
require.Equal(t, status.NotFound, parsedErr.Type(), "should return not found error")
}
+func TestSqlite_SavePeer(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.Skip("The SQLite store is not properly supported by Windows yet")
+ }
+
+ store := newSqliteStoreFromFile(t, "testdata/store.json")
+
+ account, err := store.GetAccount(context.Background(), "bf1c8084-ba50-4ce7-9439-34653001fc3b")
+ require.NoError(t, err)
+
+ // save status of non-existing peer
+ peer := &nbpeer.Peer{
+ Key: "peerkey",
+ ID: "testpeer",
+ SetupKey: "peerkeysetupkey",
+ IP: net.IP{127, 0, 0, 1},
+ Meta: nbpeer.PeerSystemMeta{Hostname: "testingpeer"},
+ Name: "peer name",
+ Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()},
+ }
+ ctx := context.Background()
+ err = store.SavePeer(ctx, account.Id, peer)
+ assert.Error(t, err)
+ parsedErr, ok := status.FromError(err)
+ require.True(t, ok)
+ require.Equal(t, status.NotFound, parsedErr.Type(), "should return not found error")
+
+ // save new status of existing peer
+ account.Peers[peer.ID] = peer
+
+ err = store.SaveAccount(context.Background(), account)
+ require.NoError(t, err)
+
+ updatedPeer := peer.Copy()
+ updatedPeer.Status.Connected = false
+ updatedPeer.Meta.Hostname = "updatedpeer"
+
+ err = store.SavePeer(ctx, account.Id, updatedPeer)
+ require.NoError(t, err)
+
+ account, err = store.GetAccount(context.Background(), account.Id)
+ require.NoError(t, err)
+
+ actual := account.Peers[peer.ID]
+ assert.Equal(t, updatedPeer.Status, actual.Status)
+ assert.Equal(t, updatedPeer.Meta, actual.Meta)
+}
+
func TestSqlite_SavePeerStatus(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("The SQLite store is not properly supported by Windows yet")
@@ -414,6 +462,7 @@ func TestSqlite_SavePeerStatus(t *testing.T) {
actual = account.Peers["testpeer"].Status
assert.Equal(t, newStatus, *actual)
}
+
func TestSqlite_SavePeerLocation(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("The SQLite store is not properly supported by Windows yet")
diff --git a/management/server/store.go b/management/server/store.go
index 3ba73e8c751..15a419c78f8 100644
--- a/management/server/store.go
+++ b/management/server/store.go
@@ -12,10 +12,11 @@ import (
"strings"
"time"
- nbgroup "github.com/netbirdio/netbird/management/server/group"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
+ nbgroup "github.com/netbirdio/netbird/management/server/group"
+
"github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/util"
@@ -54,6 +55,7 @@ type Store interface {
AcquireAccountReadLock(ctx context.Context, accountID string) func()
// AcquireGlobalLock should attempt to acquire a global lock and return a function that releases the lock
AcquireGlobalLock(ctx context.Context) func()
+ SavePeer(ctx context.Context, accountID string, peer *nbpeer.Peer) error
SavePeerStatus(accountID, peerID string, status nbpeer.PeerStatus) error
SavePeerLocation(accountID string, peer *nbpeer.Peer) error
SaveUserLastLogin(accountID, userID string, lastLogin time.Time) error
From 1a15b0f900e6e2dfc49c937b443675f2b95de74d Mon Sep 17 00:00:00 2001
From: Zoltan Papp
Date: Fri, 26 Jul 2024 16:27:51 +0200
Subject: [PATCH 35/77] Fix race issue in set listener (#2332)
---
client/internal/dns/service_listener.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/client/internal/dns/service_listener.go b/client/internal/dns/service_listener.go
index 89cf4daf6a6..e0f9da26f83 100644
--- a/client/internal/dns/service_listener.go
+++ b/client/internal/dns/service_listener.go
@@ -128,6 +128,9 @@ func (s *serviceViaListener) RuntimeIP() string {
}
func (s *serviceViaListener) setListenerStatus(running bool) {
+ s.listenerFlagLock.Lock()
+ defer s.listenerFlagLock.Unlock()
+
s.listenerIsRunning = running
}
From ea3205643a1de32522019757b848d4689664fabc Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Fri, 26 Jul 2024 16:33:20 +0200
Subject: [PATCH 36/77] Save daemon address on service install (#2328)
---
client/cmd/service_installer.go | 2 ++
1 file changed, 2 insertions(+)
diff --git a/client/cmd/service_installer.go b/client/cmd/service_installer.go
index 5e147262bb4..99a4821b0ba 100644
--- a/client/cmd/service_installer.go
+++ b/client/cmd/service_installer.go
@@ -31,6 +31,8 @@ var installCmd = &cobra.Command{
configPath,
"--log-level",
logLevel,
+ "--daemon-addr",
+ daemonAddr,
}
if managementURL != "" {
From 7321046cd6a9a16c42eceeea98e4b60b94414baa Mon Sep 17 00:00:00 2001
From: Bethuel Mmbaga
Date: Fri, 26 Jul 2024 17:33:54 +0300
Subject: [PATCH 37/77] Remove redundant check for empty JWT groups (#2323)
* Remove redundant check for empty group names in SetJWTGroups
* add test
---
management/server/account.go | 4 ----
management/server/account_test.go | 7 +++++++
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/management/server/account.go b/management/server/account.go
index 558de6fbb76..16a2851bd10 100644
--- a/management/server/account.go
+++ b/management/server/account.go
@@ -770,10 +770,6 @@ func (a *Account) GetPeer(peerID string) *nbpeer.Peer {
// SetJWTGroups updates the user's auto groups by synchronizing JWT groups.
// Returns true if there are changes in the JWT group membership.
func (a *Account) SetJWTGroups(userID string, groupsNames []string) bool {
- if len(groupsNames) == 0 {
- return false
- }
-
user, ok := a.Users[userID]
if !ok {
return false
diff --git a/management/server/account_test.go b/management/server/account_test.go
index 71b43bd65c0..45b4fbd6f5a 100644
--- a/management/server/account_test.go
+++ b/management/server/account_test.go
@@ -2219,6 +2219,13 @@ func TestAccount_SetJWTGroups(t *testing.T) {
assert.Len(t, account.Users["user2"].AutoGroups, 1, "new group should be added")
assert.Contains(t, account.Groups, account.Users["user2"].AutoGroups[0], "groups must contain group3 from user groups")
})
+
+ t.Run("remove all JWT groups", func(t *testing.T) {
+ updated := account.SetJWTGroups("user1", []string{})
+ assert.True(t, updated, "account should be updated")
+ assert.Len(t, account.Users["user1"].AutoGroups, 1, "only non-JWT groups should remain")
+ assert.Contains(t, account.Users["user1"].AutoGroups, "group1", " group1 should still be present")
+ })
}
func TestAccount_UserGroupsAddToPeers(t *testing.T) {
From da39c8bbca24785925a7a0bd0ec21ca796475ec1 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Mon, 29 Jul 2024 13:30:27 +0200
Subject: [PATCH 38/77] Refactor login with store.SavePeer (#2334)
This pull request refactors the login functionality by integrating store.SavePeer. The changes aim to improve the handling of peer login processes, particularly focusing on synchronization and error handling.
Changes:
- Refactored login logic to use store.SavePeer.
- Added checks for login without lock for login necessary checks from the client and utilized write lock for full login flow.
- Updated error handling with status.NewPeerLoginExpiredError().
- Moved geoIP check logic to a more appropriate place.
- Removed redundant calls and improved documentation.
- Moved the code to smaller methods to improve readability.
---
management/server/peer.go | 199 ++++++++++++++----------------
management/server/status/error.go | 5 +
2 files changed, 100 insertions(+), 104 deletions(-)
diff --git a/management/server/peer.go b/management/server/peer.go
index ec8b773b065..a740067a8b6 100644
--- a/management/server/peer.go
+++ b/management/server/peer.go
@@ -453,6 +453,17 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, setupKey, userID s
Location: peer.Location,
}
+ if am.geo != nil && newPeer.Location.ConnectionIP != nil {
+ location, err := am.geo.Lookup(newPeer.Location.ConnectionIP)
+ if err != nil {
+ log.WithContext(ctx).Warnf("failed to get location for new peer realip: [%s]: %v", newPeer.Location.ConnectionIP.String(), err)
+ } else {
+ newPeer.Location.CountryCode = location.Country.ISOCode
+ newPeer.Location.CityName = location.City.Names.En
+ newPeer.Location.GeoNameID = location.City.GeonameID
+ }
+ }
+
// add peer to 'All' group
group, err := account.GetGroupAll()
if err != nil {
@@ -535,7 +546,7 @@ func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync PeerSync, ac
}
if peerLoginExpired(ctx, peer, account.Settings) {
- return nil, nil, nil, status.Errorf(status.PermissionDenied, "peer login has expired, please log in once more")
+ return nil, nil, nil, status.NewPeerLoginExpiredError()
}
peer, updated := updatePeerMeta(peer, sync.Meta, account)
@@ -586,21 +597,10 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login PeerLogin)
// we couldn't find this peer by its public key which can mean that peer hasn't been registered yet.
// Try registering it.
newPeer := &nbpeer.Peer{
- Key: login.WireGuardPubKey,
- Meta: login.Meta,
- SSHKey: login.SSHKey,
- }
- if am.geo != nil && login.ConnectionIP != nil {
- location, err := am.geo.Lookup(login.ConnectionIP)
- if err != nil {
- log.WithContext(ctx).Warnf("failed to get location for new peer realip: [%s]: %v", login.ConnectionIP.String(), err)
- } else {
- newPeer.Location.ConnectionIP = login.ConnectionIP
- newPeer.Location.CountryCode = location.Country.ISOCode
- newPeer.Location.CityName = location.City.Names.En
- newPeer.Location.GeoNameID = location.City.GeonameID
-
- }
+ Key: login.WireGuardPubKey,
+ Meta: login.Meta,
+ SSHKey: login.SSHKey,
+ Location: nbpeer.Location{ConnectionIP: login.ConnectionIP},
}
return am.AddPeer(ctx, login.SetupKey, login.UserID, newPeer)
@@ -610,44 +610,17 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login PeerLogin)
return nil, nil, nil, status.Errorf(status.Internal, "failed while logging in peer")
}
- peer, err := am.Store.GetPeerByPeerPubKey(ctx, login.WireGuardPubKey)
- if err != nil {
- return nil, nil, nil, status.NewPeerNotRegisteredError()
- }
-
- accSettings, err := am.Store.GetAccountSettings(ctx, accountID)
- if err != nil {
- return nil, nil, nil, status.Errorf(status.Internal, "failed to get account settings: %s", err)
- }
-
- var isWriteLock bool
-
- // duplicated logic from after the lock to have an early exit
- expired := peerLoginExpired(ctx, peer, accSettings)
- switch {
- case expired:
- if err := checkAuth(ctx, login.UserID, peer); err != nil {
+ // when the client sends a login request with a JWT which is used to get the user ID,
+ // it means that the client has already checked if it needs login and had been through the SSO flow
+ // so, we can skip this check and directly proceed with the login
+ if login.UserID == "" {
+ err = am.checkIFPeerNeedsLoginWithoutLock(ctx, accountID, login)
+ if err != nil {
return nil, nil, nil, err
}
- isWriteLock = true
- log.WithContext(ctx).Debugf("peer login expired, acquiring write lock")
-
- case peer.UpdateMetaIfNew(login.Meta):
- isWriteLock = true
- log.WithContext(ctx).Debugf("peer changed meta, acquiring write lock")
-
- default:
- isWriteLock = false
- log.WithContext(ctx).Debugf("peer meta is the same, acquiring read lock")
}
- var unlock func()
-
- if isWriteLock {
- unlock = am.Store.AcquireAccountWriteLock(ctx, accountID)
- } else {
- unlock = am.Store.AcquireAccountReadLock(ctx, accountID)
- }
+ unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
defer func() {
if unlock != nil {
unlock()
@@ -660,7 +633,7 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login PeerLogin)
return nil, nil, nil, err
}
- peer, err = account.FindPeerByPubKey(login.WireGuardPubKey)
+ peer, err := account.FindPeerByPubKey(login.WireGuardPubKey)
if err != nil {
return nil, nil, nil, status.NewPeerNotRegisteredError()
}
@@ -671,53 +644,39 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login PeerLogin)
}
// this flag prevents unnecessary calls to the persistent store.
- shouldStoreAccount := false
+ shouldStorePeer := false
updateRemotePeers := false
if peerLoginExpired(ctx, peer, account.Settings) {
- err = checkAuth(ctx, login.UserID, peer)
+ err = am.handleExpiredPeer(ctx, login, account, peer)
if err != nil {
return nil, nil, nil, err
}
- // If peer was expired before and if it reached this point, it is re-authenticated.
- // UserID is present, meaning that JWT validation passed successfully in the API layer.
- updatePeerLastLogin(peer, account)
updateRemotePeers = true
- shouldStoreAccount = true
-
- // sync user last login with peer last login
- user, err := account.FindUser(login.UserID)
- if err != nil {
- return nil, nil, nil, status.Errorf(status.Internal, "couldn't find user")
- }
- user.updateLastLogin(peer.LastLogin)
-
- am.StoreEvent(ctx, login.UserID, peer.ID, account.Id, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain()))
+ shouldStorePeer = true
}
isRequiresApproval, isStatusChanged, err := am.integratedPeerValidator.IsNotValidPeer(ctx, account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra)
if err != nil {
return nil, nil, nil, err
}
+
peer, updated := updatePeerMeta(peer, login.Meta, account)
if updated {
- shouldStoreAccount = true
+ shouldStorePeer = true
}
- peer, err = am.checkAndUpdatePeerSSHKey(ctx, peer, account, login.SSHKey)
- if err != nil {
- return nil, nil, nil, err
+ if peer.SSHKey != login.SSHKey {
+ peer.SSHKey = login.SSHKey
+ shouldStorePeer = true
}
- if shouldStoreAccount {
- if !isWriteLock {
- log.WithContext(ctx).Errorf("account %s should be stored but is not write locked", accountID)
- return nil, nil, nil, status.Errorf(status.Internal, "account should be stored but is not write locked")
- }
- err = am.Store.SaveAccount(ctx, account)
+ if shouldStorePeer {
+ err = am.Store.SavePeer(ctx, accountID, peer)
if err != nil {
return nil, nil, nil, err
}
}
+
unlock()
unlock = nil
@@ -725,13 +684,46 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login PeerLogin)
am.updateAccountPeers(ctx, account)
}
+ return am.getValidatedPeerWithMap(ctx, isRequiresApproval, account, peer)
+}
+
+// checkIFPeerNeedsLoginWithoutLock checks if the peer needs login without acquiring the account lock. The check validate if the peer was not added via SSO
+// and if the peer login is expired.
+// The NetBird client doesn't have a way to check if the peer needs login besides sending a login request
+// with no JWT token and usually no setup-key. As the client can send up to two login request to check if it is expired
+// and before starting the engine, we do the checks without an account lock to avoid piling up requests.
+func (am *DefaultAccountManager) checkIFPeerNeedsLoginWithoutLock(ctx context.Context, accountID string, login PeerLogin) error {
+ peer, err := am.Store.GetPeerByPeerPubKey(ctx, login.WireGuardPubKey)
+ if err != nil {
+ return err
+ }
+
+ // if the peer was not added with SSO login we can exit early because peers activated with setup-key
+ // doesn't expire, and we avoid extra databases calls.
+ if !peer.AddedWithSSOLogin() {
+ return nil
+ }
+
+ settings, err := am.Store.GetAccountSettings(ctx, accountID)
+ if err != nil {
+ return err
+ }
+
+ if peerLoginExpired(ctx, peer, settings) {
+ return status.NewPeerLoginExpiredError()
+ }
+
+ return nil
+}
+
+func (am *DefaultAccountManager) getValidatedPeerWithMap(ctx context.Context, isRequiresApproval bool, account *Account, peer *nbpeer.Peer) (*nbpeer.Peer, *NetworkMap, []*posture.Checks, error) {
var postureChecks []*posture.Checks
if isRequiresApproval {
emptyMap := &NetworkMap{
Network: account.Network.Copy(),
}
- return peer, emptyMap, postureChecks, nil
+ return peer, emptyMap, nil, nil
}
approvedPeersMap, err := am.GetValidatedPeers(account)
@@ -743,6 +735,30 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login PeerLogin)
return peer, account.GetPeerNetworkMap(ctx, peer.ID, am.dnsDomain, approvedPeersMap), postureChecks, nil
}
+func (am *DefaultAccountManager) handleExpiredPeer(ctx context.Context, login PeerLogin, account *Account, peer *nbpeer.Peer) error {
+ err := checkAuth(ctx, login.UserID, peer)
+ if err != nil {
+ return err
+ }
+ // If peer was expired before and if it reached this point, it is re-authenticated.
+ // UserID is present, meaning that JWT validation passed successfully in the API layer.
+ updatePeerLastLogin(peer, account)
+
+ // sync user last login with peer last login
+ user, err := account.FindUser(login.UserID)
+ if err != nil {
+ return status.Errorf(status.Internal, "couldn't find user")
+ }
+
+ err = am.Store.SaveUserLastLogin(account.Id, user.Id, peer.LastLogin)
+ if err != nil {
+ return err
+ }
+
+ am.StoreEvent(ctx, login.UserID, peer.ID, account.Id, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain()))
+ return nil
+}
+
func checkIfPeerOwnerIsBlocked(peer *nbpeer.Peer, account *Account) error {
if peer.AddedWithSSOLogin() {
user, err := account.FindUser(peer.UserID)
@@ -759,11 +775,11 @@ func checkIfPeerOwnerIsBlocked(peer *nbpeer.Peer, account *Account) error {
func checkAuth(ctx context.Context, loginUserID string, peer *nbpeer.Peer) error {
if loginUserID == "" {
// absence of a user ID indicates that JWT wasn't provided.
- return status.Errorf(status.PermissionDenied, "peer login has expired, please log in once more")
+ return status.NewPeerLoginExpiredError()
}
if peer.UserID != loginUserID {
log.WithContext(ctx).Warnf("user mismatch when logging in peer %s: peer user %s, login user %s ", peer.ID, peer.UserID, loginUserID)
- return status.Errorf(status.Unauthenticated, "can't login")
+ return status.Errorf(status.Unauthenticated, "can't login with this credentials")
}
return nil
}
@@ -783,31 +799,6 @@ func updatePeerLastLogin(peer *nbpeer.Peer, account *Account) {
account.UpdatePeer(peer)
}
-func (am *DefaultAccountManager) checkAndUpdatePeerSSHKey(ctx context.Context, peer *nbpeer.Peer, account *Account, newSSHKey string) (*nbpeer.Peer, error) {
- if len(newSSHKey) == 0 {
- log.WithContext(ctx).Debugf("no new SSH key provided for peer %s, skipping update", peer.ID)
- return peer, nil
- }
-
- if peer.SSHKey == newSSHKey {
- log.WithContext(ctx).Debugf("same SSH key provided for peer %s, skipping update", peer.ID)
- return peer, nil
- }
-
- peer.SSHKey = newSSHKey
- account.UpdatePeer(peer)
-
- err := am.Store.SaveAccount(ctx, account)
- if err != nil {
- return nil, err
- }
-
- // trigger network map update
- am.updateAccountPeers(ctx, account)
-
- return peer, nil
-}
-
// UpdatePeerSSHKey updates peer's public SSH key
func (am *DefaultAccountManager) UpdatePeerSSHKey(ctx context.Context, peerID string, sshKey string) error {
if sshKey == "" {
diff --git a/management/server/status/error.go b/management/server/status/error.go
index 39cd6c613e2..58b9a84a0cc 100644
--- a/management/server/status/error.go
+++ b/management/server/status/error.go
@@ -95,3 +95,8 @@ func NewUserNotFoundError(userKey string) error {
func NewPeerNotRegisteredError() error {
return Errorf(Unauthenticated, "peer is not registered")
}
+
+// NewPeerLoginExpiredError creates a new Error with PermissionDenied type for an expired peer
+func NewPeerLoginExpiredError() error {
+ return Errorf(PermissionDenied, "peer login has expired, please log in once more")
+}
From 9d2047a08af6b68bbd65382ab5f276567f6ac994 Mon Sep 17 00:00:00 2001
From: Evgenii
Date: Wed, 31 Jul 2024 08:58:04 +0100
Subject: [PATCH 39/77] Fix freebsd tests (#2346)
---
.github/workflows/golang-test-freebsd.yml | 24 ++++++++++-------------
client/ui/font_bsd.go | 2 +-
2 files changed, 11 insertions(+), 15 deletions(-)
diff --git a/.github/workflows/golang-test-freebsd.yml b/.github/workflows/golang-test-freebsd.yml
index 15fc6a729ad..6cb5f7427fa 100644
--- a/.github/workflows/golang-test-freebsd.yml
+++ b/.github/workflows/golang-test-freebsd.yml
@@ -13,7 +13,7 @@ concurrency:
jobs:
test:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Test in FreeBSD
@@ -21,19 +21,15 @@ jobs:
uses: vmactions/freebsd-vm@v1
with:
usesh: true
+ copyback: false
+ release: "14.1"
prepare: |
- pkg install -y curl
- pkg install -y git
+ pkg install -y go
+ # -x - to print all executed commands
+ # -e - to faile on first error
run: |
- set -x
- curl -o go.tar.gz https://go.dev/dl/go1.21.11.freebsd-amd64.tar.gz -L
- tar zxf go.tar.gz
- mv go /usr/local/go
- ln -s /usr/local/go/bin/go /usr/local/bin/go
- go mod tidy
- go test -timeout 5m -p 1 ./iface/...
- go test -timeout 5m -p 1 ./client/...
- cd client
- go build .
- cd ..
\ No newline at end of file
+ set -e -x
+ go build -o netbird client/main.go
+ go test -timeout 5m -p 1 -failfast ./iface/...
+ go test -timeout 5m -p 1 -failfast ./client/...
diff --git a/client/ui/font_bsd.go b/client/ui/font_bsd.go
index 41bccceca6c..84cb5993ddc 100644
--- a/client/ui/font_bsd.go
+++ b/client/ui/font_bsd.go
@@ -1,4 +1,4 @@
-//go:build darwin || dragonfly || freebsd || netbsd || openbsd
+//go:build darwin
package main
From 165988429c5406bd721f0813c26977fafc41862a Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Wed, 31 Jul 2024 14:53:32 +0200
Subject: [PATCH 40/77] Add write lock for peer when saving its connection
status (#2359)
---
management/server/account.go | 28 +++++++++++++----------
management/server/dns.go | 4 ++--
management/server/event.go | 2 +-
management/server/file_store.go | 20 ++++++++--------
management/server/group.go | 16 ++++++-------
management/server/integrated_validator.go | 2 +-
management/server/nameserver.go | 10 ++++----
management/server/peer.go | 14 ++++++------
management/server/policy.go | 8 +++----
management/server/posture_checks.go | 8 +++----
management/server/route.go | 10 ++++----
management/server/setupkey.go | 8 +++----
management/server/sql_store.go | 20 ++++++++--------
management/server/store.go | 8 +++----
management/server/user.go | 22 +++++++++---------
15 files changed, 93 insertions(+), 87 deletions(-)
diff --git a/management/server/account.go b/management/server/account.go
index 16a2851bd10..b2b23dcb9a6 100644
--- a/management/server/account.go
+++ b/management/server/account.go
@@ -974,7 +974,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
return nil, status.Errorf(status.InvalidArgument, "peer login expiration can't be smaller than one hour")
}
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -1025,7 +1025,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
func (am *DefaultAccountManager) peerLoginExpirationJob(ctx context.Context, accountID string) func() (time.Duration, bool) {
return func() (time.Duration, bool) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -1124,7 +1124,7 @@ func (am *DefaultAccountManager) warmupIDPCache(ctx context.Context) error {
// DeleteAccount deletes an account and all its users from local store and from the remote IDP if the requester is an admin and account owner
func (am *DefaultAccountManager) DeleteAccount(ctx context.Context, accountID, userID string) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
if err != nil {
@@ -1584,7 +1584,7 @@ func (am *DefaultAccountManager) MarkPATUsed(ctx context.Context, tokenID string
return err
}
- unlock := am.Store.AcquireAccountWriteLock(ctx, account.Id)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, account.Id)
defer unlock()
account, err = am.Store.GetAccountByUser(ctx, user.Id)
@@ -1667,7 +1667,7 @@ func (am *DefaultAccountManager) GetAccountFromToken(ctx context.Context, claims
if err != nil {
return nil, nil, err
}
- unlock := am.Store.AcquireAccountWriteLock(ctx, newAcc.Id)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, newAcc.Id)
alreadyUnlocked := false
defer func() {
if !alreadyUnlocked {
@@ -1823,7 +1823,7 @@ func (am *DefaultAccountManager) getAccountWithAuthorizationClaims(ctx context.C
account, err := am.Store.GetAccountByUser(ctx, claims.UserId)
if err == nil {
- unlockAccount := am.Store.AcquireAccountWriteLock(ctx, account.Id)
+ unlockAccount := am.Store.AcquireWriteLockByUID(ctx, account.Id)
defer unlockAccount()
account, err = am.Store.GetAccountByUser(ctx, claims.UserId)
if err != nil {
@@ -1843,7 +1843,7 @@ func (am *DefaultAccountManager) getAccountWithAuthorizationClaims(ctx context.C
return account, nil
} else if s, ok := status.FromError(err); ok && s.Type() == status.NotFound {
if domainAccount != nil {
- unlockAccount := am.Store.AcquireAccountWriteLock(ctx, domainAccount.Id)
+ unlockAccount := am.Store.AcquireWriteLockByUID(ctx, domainAccount.Id)
defer unlockAccount()
domainAccount, err = am.Store.GetAccountByPrivateDomain(ctx, claims.Domain)
if err != nil {
@@ -1866,8 +1866,10 @@ func (am *DefaultAccountManager) SyncAndMarkPeer(ctx context.Context, peerPubKey
return nil, nil, nil, err
}
- unlock := am.Store.AcquireAccountReadLock(ctx, accountID)
- defer unlock()
+ accountUnlock := am.Store.AcquireReadLockByUID(ctx, accountID)
+ defer accountUnlock()
+ peerUnlock := am.Store.AcquireWriteLockByUID(ctx, peerPubKey)
+ defer peerUnlock()
account, err := am.Store.GetAccount(ctx, accountID)
if err != nil {
@@ -1896,8 +1898,10 @@ func (am *DefaultAccountManager) CancelPeerRoutines(ctx context.Context, peer *n
return err
}
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
- defer unlock()
+ accountUnlock := am.Store.AcquireReadLockByUID(ctx, accountID)
+ defer accountUnlock()
+ peerUnlock := am.Store.AcquireWriteLockByUID(ctx, peer.Key)
+ defer peerUnlock()
account, err := am.Store.GetAccount(ctx, accountID)
if err != nil {
@@ -1919,7 +1923,7 @@ func (am *DefaultAccountManager) SyncPeerMeta(ctx context.Context, peerPubKey st
return err
}
- unlock := am.Store.AcquireAccountReadLock(ctx, accountID)
+ unlock := am.Store.AcquireReadLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
diff --git a/management/server/dns.go b/management/server/dns.go
index 8a889df3f99..08732ad78ec 100644
--- a/management/server/dns.go
+++ b/management/server/dns.go
@@ -36,7 +36,7 @@ func (d DNSSettings) Copy() DNSSettings {
// GetDNSSettings validates a user role and returns the DNS settings for the provided account ID
func (am *DefaultAccountManager) GetDNSSettings(ctx context.Context, accountID string, userID string) (*DNSSettings, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -58,7 +58,7 @@ func (am *DefaultAccountManager) GetDNSSettings(ctx context.Context, accountID s
// SaveDNSSettings validates a user role and updates the account's DNS settings
func (am *DefaultAccountManager) SaveDNSSettings(ctx context.Context, accountID string, userID string, dnsSettingsToSave *DNSSettings) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
diff --git a/management/server/event.go b/management/server/event.go
index 616cea28772..93b80922678 100644
--- a/management/server/event.go
+++ b/management/server/event.go
@@ -13,7 +13,7 @@ import (
// GetEvents returns a list of activity events of an account
func (am *DefaultAccountManager) GetEvents(ctx context.Context, accountID, userID string) ([]*activity.Event, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
diff --git a/management/server/file_store.go b/management/server/file_store.go
index 9a146283279..6e3536bcdf6 100644
--- a/management/server/file_store.go
+++ b/management/server/file_store.go
@@ -39,8 +39,8 @@ type FileStore struct {
mux sync.Mutex `json:"-"`
storeFile string `json:"-"`
- // sync.Mutex indexed by accountID
- accountLocks sync.Map `json:"-"`
+ // sync.Mutex indexed by resource ID
+ resourceLocks sync.Map `json:"-"`
globalAccountLock sync.Mutex `json:"-"`
metrics telemetry.AppMetrics `json:"-"`
@@ -281,26 +281,26 @@ func (s *FileStore) AcquireGlobalLock(ctx context.Context) (unlock func()) {
return unlock
}
-// AcquireAccountWriteLock acquires account lock for writing to a resource and returns a function that releases the lock
-func (s *FileStore) AcquireAccountWriteLock(ctx context.Context, accountID string) (unlock func()) {
- log.WithContext(ctx).Debugf("acquiring lock for account %s", accountID)
+// AcquireWriteLockByUID acquires an ID lock for writing to a resource and returns a function that releases the lock
+func (s *FileStore) AcquireWriteLockByUID(ctx context.Context, uniqueID string) (unlock func()) {
+ log.WithContext(ctx).Debugf("acquiring lock for ID %s", uniqueID)
start := time.Now()
- value, _ := s.accountLocks.LoadOrStore(accountID, &sync.Mutex{})
+ value, _ := s.resourceLocks.LoadOrStore(uniqueID, &sync.Mutex{})
mtx := value.(*sync.Mutex)
mtx.Lock()
unlock = func() {
mtx.Unlock()
- log.WithContext(ctx).Debugf("released lock for account %s in %v", accountID, time.Since(start))
+ log.WithContext(ctx).Debugf("released lock for ID %s in %v", uniqueID, time.Since(start))
}
return unlock
}
-// AcquireAccountReadLock AcquireAccountWriteLock acquires account lock for reading a resource and returns a function that releases the lock
+// AcquireReadLockByUID acquires an ID lock for reading a resource and returns a function that releases the lock
// This method is still returns a write lock as file store can't handle read locks
-func (s *FileStore) AcquireAccountReadLock(ctx context.Context, accountID string) (unlock func()) {
- return s.AcquireAccountWriteLock(ctx, accountID)
+func (s *FileStore) AcquireReadLockByUID(ctx context.Context, uniqueID string) (unlock func()) {
+ return s.AcquireWriteLockByUID(ctx, uniqueID)
}
func (s *FileStore) SaveAccount(ctx context.Context, account *Account) error {
diff --git a/management/server/group.go b/management/server/group.go
index 45c51bda237..d7711c1268b 100644
--- a/management/server/group.go
+++ b/management/server/group.go
@@ -23,7 +23,7 @@ func (e *GroupLinkError) Error() string {
// GetGroup object of the peers
func (am *DefaultAccountManager) GetGroup(ctx context.Context, accountID, groupID, userID string) (*nbgroup.Group, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -50,7 +50,7 @@ func (am *DefaultAccountManager) GetGroup(ctx context.Context, accountID, groupI
// GetAllGroups returns all groups in an account
func (am *DefaultAccountManager) GetAllGroups(ctx context.Context, accountID string, userID string) ([]*nbgroup.Group, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -77,7 +77,7 @@ func (am *DefaultAccountManager) GetAllGroups(ctx context.Context, accountID str
// GetGroupByName filters all groups in an account by name and returns the one with the most peers
func (am *DefaultAccountManager) GetGroupByName(ctx context.Context, groupName, accountID string) (*nbgroup.Group, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -110,7 +110,7 @@ func (am *DefaultAccountManager) GetGroupByName(ctx context.Context, groupName,
// SaveGroup object of the peers
func (am *DefaultAccountManager) SaveGroup(ctx context.Context, accountID, userID string, newGroup *nbgroup.Group) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
return am.SaveGroups(ctx, accountID, userID, []*nbgroup.Group{newGroup})
}
@@ -245,7 +245,7 @@ func difference(a, b []string) []string {
// DeleteGroup object of the peers
func (am *DefaultAccountManager) DeleteGroup(ctx context.Context, accountId, userId, groupID string) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountId)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountId)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountId)
@@ -359,7 +359,7 @@ func (am *DefaultAccountManager) DeleteGroup(ctx context.Context, accountId, use
// ListGroups objects of the peers
func (am *DefaultAccountManager) ListGroups(ctx context.Context, accountID string) ([]*nbgroup.Group, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -377,7 +377,7 @@ func (am *DefaultAccountManager) ListGroups(ctx context.Context, accountID strin
// GroupAddPeer appends peer to the group
func (am *DefaultAccountManager) GroupAddPeer(ctx context.Context, accountID, groupID, peerID string) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -413,7 +413,7 @@ func (am *DefaultAccountManager) GroupAddPeer(ctx context.Context, accountID, gr
// GroupDeletePeer removes peer from the group
func (am *DefaultAccountManager) GroupDeletePeer(ctx context.Context, accountID, groupID, peerID string) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
diff --git a/management/server/integrated_validator.go b/management/server/integrated_validator.go
index 05537ada4aa..99e6b204c2b 100644
--- a/management/server/integrated_validator.go
+++ b/management/server/integrated_validator.go
@@ -32,7 +32,7 @@ func (am *DefaultAccountManager) UpdateIntegratedValidatorGroups(ctx context.Con
return errors.New("invalid groups")
}
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
a, err := am.Store.GetAccountByUser(ctx, userID)
diff --git a/management/server/nameserver.go b/management/server/nameserver.go
index f8d644dedb8..636f7cfee43 100644
--- a/management/server/nameserver.go
+++ b/management/server/nameserver.go
@@ -20,7 +20,7 @@ const domainPattern = `^(?i)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$`
// GetNameServerGroup gets a nameserver group object from account and nameserver group IDs
func (am *DefaultAccountManager) GetNameServerGroup(ctx context.Context, accountID, userID, nsGroupID string) (*nbdns.NameServerGroup, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -48,7 +48,7 @@ func (am *DefaultAccountManager) GetNameServerGroup(ctx context.Context, account
// CreateNameServerGroup creates and saves a new nameserver group
func (am *DefaultAccountManager) CreateNameServerGroup(ctx context.Context, accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string, searchDomainEnabled bool) (*nbdns.NameServerGroup, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -95,7 +95,7 @@ func (am *DefaultAccountManager) CreateNameServerGroup(ctx context.Context, acco
// SaveNameServerGroup saves nameserver group
func (am *DefaultAccountManager) SaveNameServerGroup(ctx context.Context, accountID, userID string, nsGroupToSave *nbdns.NameServerGroup) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
if nsGroupToSave == nil {
@@ -130,7 +130,7 @@ func (am *DefaultAccountManager) SaveNameServerGroup(ctx context.Context, accoun
// DeleteNameServerGroup deletes nameserver group with nsGroupID
func (am *DefaultAccountManager) DeleteNameServerGroup(ctx context.Context, accountID, nsGroupID, userID string) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -160,7 +160,7 @@ func (am *DefaultAccountManager) DeleteNameServerGroup(ctx context.Context, acco
// ListNameServerGroups returns a list of nameserver groups from account
func (am *DefaultAccountManager) ListNameServerGroups(ctx context.Context, accountID string, userID string) ([]*nbdns.NameServerGroup, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
diff --git a/management/server/peer.go b/management/server/peer.go
index a740067a8b6..998a9e53b3b 100644
--- a/management/server/peer.go
+++ b/management/server/peer.go
@@ -150,7 +150,7 @@ func (am *DefaultAccountManager) MarkPeerConnected(ctx context.Context, peerPubK
// UpdatePeer updates peer. Only Peer.Name, Peer.SSHEnabled, and Peer.LoginExpirationEnabled can be updated.
func (am *DefaultAccountManager) UpdatePeer(ctx context.Context, accountID, userID string, update *nbpeer.Peer) (*nbpeer.Peer, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -272,7 +272,7 @@ func (am *DefaultAccountManager) deletePeers(ctx context.Context, account *Accou
// DeletePeer removes peer from the account by its IP
func (am *DefaultAccountManager) DeletePeer(ctx context.Context, accountID, peerID, userID string) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -356,7 +356,7 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, setupKey, userID s
return nil, nil, nil, status.Errorf(status.NotFound, "failed adding new peer: account not found")
}
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer func() {
if unlock != nil {
unlock()
@@ -380,7 +380,7 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, setupKey, userID s
}
// This is a handling for the case when the same machine (with the same WireGuard pub key) tries to register twice.
- // Such case is possible when AddPeer function takes long time to finish after AcquireAccountWriteLock (e.g., database is slow)
+ // Such case is possible when AddPeer function takes long time to finish after AcquireWriteLockByUID (e.g., database is slow)
// and the peer disconnects with a timeout and tries to register again.
// We just check if this machine has been registered before and reject the second registration.
// The connecting peer should be able to recover with a retry.
@@ -620,7 +620,7 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login PeerLogin)
}
}
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer func() {
if unlock != nil {
unlock()
@@ -811,7 +811,7 @@ func (am *DefaultAccountManager) UpdatePeerSSHKey(ctx context.Context, peerID st
return err
}
- unlock := am.Store.AcquireAccountWriteLock(ctx, account.Id)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, account.Id)
defer unlock()
// ensure that we consider modification happened meanwhile (because we were outside the account lock when we fetched the account)
@@ -846,7 +846,7 @@ func (am *DefaultAccountManager) UpdatePeerSSHKey(ctx context.Context, peerID st
// GetPeer for a given accountID, peerID and userID error if not found.
func (am *DefaultAccountManager) GetPeer(ctx context.Context, accountID, peerID, userID string) (*nbpeer.Peer, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
diff --git a/management/server/policy.go b/management/server/policy.go
index a70d7f0ed07..30614ed2dd5 100644
--- a/management/server/policy.go
+++ b/management/server/policy.go
@@ -315,7 +315,7 @@ func (a *Account) connResourcesGenerator(ctx context.Context) (func(*PolicyRule,
// GetPolicy from the store
func (am *DefaultAccountManager) GetPolicy(ctx context.Context, accountID, policyID, userID string) (*Policy, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -343,7 +343,7 @@ func (am *DefaultAccountManager) GetPolicy(ctx context.Context, accountID, polic
// SavePolicy in the store
func (am *DefaultAccountManager) SavePolicy(ctx context.Context, accountID, userID string, policy *Policy) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -371,7 +371,7 @@ func (am *DefaultAccountManager) SavePolicy(ctx context.Context, accountID, user
// DeletePolicy from the store
func (am *DefaultAccountManager) DeletePolicy(ctx context.Context, accountID, policyID, userID string) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -398,7 +398,7 @@ func (am *DefaultAccountManager) DeletePolicy(ctx context.Context, accountID, po
// ListPolicies from the store
func (am *DefaultAccountManager) ListPolicies(ctx context.Context, accountID, userID string) ([]*Policy, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
diff --git a/management/server/posture_checks.go b/management/server/posture_checks.go
index 851d4d31f22..4a7c9755da3 100644
--- a/management/server/posture_checks.go
+++ b/management/server/posture_checks.go
@@ -15,7 +15,7 @@ const (
)
func (am *DefaultAccountManager) GetPostureChecks(ctx context.Context, accountID, postureChecksID, userID string) (*posture.Checks, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -42,7 +42,7 @@ func (am *DefaultAccountManager) GetPostureChecks(ctx context.Context, accountID
}
func (am *DefaultAccountManager) SavePostureChecks(ctx context.Context, accountID, userID string, postureChecks *posture.Checks) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -89,7 +89,7 @@ func (am *DefaultAccountManager) SavePostureChecks(ctx context.Context, accountI
}
func (am *DefaultAccountManager) DeletePostureChecks(ctx context.Context, accountID, postureChecksID, userID string) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -121,7 +121,7 @@ func (am *DefaultAccountManager) DeletePostureChecks(ctx context.Context, accoun
}
func (am *DefaultAccountManager) ListPostureChecks(ctx context.Context, accountID, userID string) ([]*posture.Checks, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
diff --git a/management/server/route.go b/management/server/route.go
index 6db00a25512..064f3c10596 100644
--- a/management/server/route.go
+++ b/management/server/route.go
@@ -17,7 +17,7 @@ import (
// GetRoute gets a route object from account and route IDs
func (am *DefaultAccountManager) GetRoute(ctx context.Context, accountID string, routeID route.ID, userID string) (*route.Route, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -126,7 +126,7 @@ func getRouteDescriptor(prefix netip.Prefix, domains domain.List) string {
// CreateRoute creates and saves a new route
func (am *DefaultAccountManager) CreateRoute(ctx context.Context, accountID string, prefix netip.Prefix, networkType route.NetworkType, domains domain.List, peerID string, peerGroupIDs []string, description string, netID route.NetID, masquerade bool, metric int, groups []string, enabled bool, userID string, keepRoute bool) (*route.Route, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -214,7 +214,7 @@ func (am *DefaultAccountManager) CreateRoute(ctx context.Context, accountID stri
// SaveRoute saves route
func (am *DefaultAccountManager) SaveRoute(ctx context.Context, accountID, userID string, routeToSave *route.Route) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
if routeToSave == nil {
@@ -283,7 +283,7 @@ func (am *DefaultAccountManager) SaveRoute(ctx context.Context, accountID, userI
// DeleteRoute deletes route with routeID
func (am *DefaultAccountManager) DeleteRoute(ctx context.Context, accountID string, routeID route.ID, userID string) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -311,7 +311,7 @@ func (am *DefaultAccountManager) DeleteRoute(ctx context.Context, accountID stri
// ListRoutes returns a list of routes from account
func (am *DefaultAccountManager) ListRoutes(ctx context.Context, accountID, userID string) ([]*route.Route, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
diff --git a/management/server/setupkey.go b/management/server/setupkey.go
index dcaee357c5e..8ef91755c15 100644
--- a/management/server/setupkey.go
+++ b/management/server/setupkey.go
@@ -210,7 +210,7 @@ func Hash(s string) uint32 {
// and adds it to the specified account. A list of autoGroups IDs can be empty.
func (am *DefaultAccountManager) CreateSetupKey(ctx context.Context, accountID string, keyName string, keyType SetupKeyType,
expiresIn time.Duration, autoGroups []string, usageLimit int, userID string, ephemeral bool) (*SetupKey, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
keyDuration := DefaultSetupKeyDuration
@@ -256,7 +256,7 @@ func (am *DefaultAccountManager) CreateSetupKey(ctx context.Context, accountID s
// (e.g. the key itself, creation date, ID, etc).
// These properties are overwritten: Name, AutoGroups, Revoked. The rest is copied from the existing key.
func (am *DefaultAccountManager) SaveSetupKey(ctx context.Context, accountID string, keyToSave *SetupKey, userID string) (*SetupKey, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
if keyToSave == nil {
@@ -328,7 +328,7 @@ func (am *DefaultAccountManager) SaveSetupKey(ctx context.Context, accountID str
// ListSetupKeys returns a list of all setup keys of the account
func (am *DefaultAccountManager) ListSetupKeys(ctx context.Context, accountID, userID string) ([]*SetupKey, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
if err != nil {
@@ -360,7 +360,7 @@ func (am *DefaultAccountManager) ListSetupKeys(ctx context.Context, accountID, u
// GetSetupKey looks up a SetupKey by KeyID, returns NotFound error if not found.
func (am *DefaultAccountManager) GetSetupKey(ctx context.Context, accountID, userID, keyID string) (*SetupKey, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
diff --git a/management/server/sql_store.go b/management/server/sql_store.go
index 7648538c3a4..c44ab7f0951 100644
--- a/management/server/sql_store.go
+++ b/management/server/sql_store.go
@@ -40,7 +40,7 @@ const (
// SqlStore represents an account storage backed by a Sql DB persisted to disk
type SqlStore struct {
db *gorm.DB
- accountLocks sync.Map
+ resourceLocks sync.Map
globalAccountLock sync.Mutex
metrics telemetry.AppMetrics
installationPK int
@@ -98,33 +98,35 @@ func (s *SqlStore) AcquireGlobalLock(ctx context.Context) (unlock func()) {
return unlock
}
-func (s *SqlStore) AcquireAccountWriteLock(ctx context.Context, accountID string) (unlock func()) {
- log.WithContext(ctx).Tracef("acquiring write lock for account %s", accountID)
+// AcquireWriteLockByUID acquires an ID lock for writing to a resource and returns a function that releases the lock
+func (s *SqlStore) AcquireWriteLockByUID(ctx context.Context, uniqueID string) (unlock func()) {
+ log.WithContext(ctx).Tracef("acquiring write lock for ID %s", uniqueID)
start := time.Now()
- value, _ := s.accountLocks.LoadOrStore(accountID, &sync.RWMutex{})
+ value, _ := s.resourceLocks.LoadOrStore(uniqueID, &sync.RWMutex{})
mtx := value.(*sync.RWMutex)
mtx.Lock()
unlock = func() {
mtx.Unlock()
- log.WithContext(ctx).Tracef("released write lock for account %s in %v", accountID, time.Since(start))
+ log.WithContext(ctx).Tracef("released write lock for ID %s in %v", uniqueID, time.Since(start))
}
return unlock
}
-func (s *SqlStore) AcquireAccountReadLock(ctx context.Context, accountID string) (unlock func()) {
- log.WithContext(ctx).Tracef("acquiring read lock for account %s", accountID)
+// AcquireReadLockByUID acquires an ID lock for writing to a resource and returns a function that releases the lock
+func (s *SqlStore) AcquireReadLockByUID(ctx context.Context, uniqueID string) (unlock func()) {
+ log.WithContext(ctx).Tracef("acquiring read lock for ID %s", uniqueID)
start := time.Now()
- value, _ := s.accountLocks.LoadOrStore(accountID, &sync.RWMutex{})
+ value, _ := s.resourceLocks.LoadOrStore(uniqueID, &sync.RWMutex{})
mtx := value.(*sync.RWMutex)
mtx.RLock()
unlock = func() {
mtx.RUnlock()
- log.WithContext(ctx).Tracef("released read lock for account %s in %v", accountID, time.Since(start))
+ log.WithContext(ctx).Tracef("released read lock for ID %s in %v", uniqueID, time.Since(start))
}
return unlock
diff --git a/management/server/store.go b/management/server/store.go
index 15a419c78f8..864871c8e53 100644
--- a/management/server/store.go
+++ b/management/server/store.go
@@ -49,10 +49,10 @@ type Store interface {
DeleteTokenID2UserIDIndex(tokenID string) error
GetInstallationID() string
SaveInstallationID(ctx context.Context, ID string) error
- // AcquireAccountWriteLock should attempt to acquire account lock for write purposes and return a function that releases the lock
- AcquireAccountWriteLock(ctx context.Context, accountID string) func()
- // AcquireAccountReadLock should attempt to acquire account lock for read purposes and return a function that releases the lock
- AcquireAccountReadLock(ctx context.Context, accountID string) func()
+ // AcquireWriteLockByUID should attempt to acquire a lock for write purposes and return a function that releases the lock
+ AcquireWriteLockByUID(ctx context.Context, uniqueID string) func()
+ // AcquireReadLockByUID should attempt to acquire lock for read purposes and return a function that releases the lock
+ AcquireReadLockByUID(ctx context.Context, uniqueID string) func()
// AcquireGlobalLock should attempt to acquire a global lock and return a function that releases the lock
AcquireGlobalLock(ctx context.Context) func()
SavePeer(ctx context.Context, accountID string, peer *nbpeer.Peer) error
diff --git a/management/server/user.go b/management/server/user.go
index 65b5c787875..2cb6b45aaaa 100644
--- a/management/server/user.go
+++ b/management/server/user.go
@@ -211,7 +211,7 @@ func NewOwnerUser(id string) *User {
// createServiceUser creates a new service user under the given account.
func (am *DefaultAccountManager) createServiceUser(ctx context.Context, accountID string, initiatorUserID string, role UserRole, serviceUserName string, nonDeletable bool, autoGroups []string) (*UserInfo, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -267,7 +267,7 @@ func (am *DefaultAccountManager) CreateUser(ctx context.Context, accountID, user
// inviteNewUser Invites a USer to a given account and creates reference in datastore
func (am *DefaultAccountManager) inviteNewUser(ctx context.Context, accountID, userID string, invite *UserInfo) (*UserInfo, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
if am.idpManager == nil {
@@ -368,7 +368,7 @@ func (am *DefaultAccountManager) GetUser(ctx context.Context, claims jwtclaims.A
return nil, fmt.Errorf("failed to get account with token claims %v", err)
}
- unlock := am.Store.AcquireAccountWriteLock(ctx, account.Id)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, account.Id)
defer unlock()
account, err = am.Store.GetAccount(ctx, account.Id)
@@ -401,7 +401,7 @@ func (am *DefaultAccountManager) GetUser(ctx context.Context, claims jwtclaims.A
// ListUsers returns lists of all users under the account.
// It doesn't populate user information such as email or name.
func (am *DefaultAccountManager) ListUsers(ctx context.Context, accountID string) ([]*User, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -428,7 +428,7 @@ func (am *DefaultAccountManager) DeleteUser(ctx context.Context, accountID, init
if initiatorUserID == targetUserID {
return status.Errorf(status.InvalidArgument, "self deletion is not allowed")
}
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -538,7 +538,7 @@ func (am *DefaultAccountManager) deleteUserPeers(ctx context.Context, initiatorU
// InviteUser resend invitations to users who haven't activated their accounts prior to the expiration period.
func (am *DefaultAccountManager) InviteUser(ctx context.Context, accountID string, initiatorUserID string, targetUserID string) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
if am.idpManager == nil {
@@ -578,7 +578,7 @@ func (am *DefaultAccountManager) InviteUser(ctx context.Context, accountID strin
// CreatePAT creates a new PAT for the given user
func (am *DefaultAccountManager) CreatePAT(ctx context.Context, accountID string, initiatorUserID string, targetUserID string, tokenName string, expiresIn int) (*PersonalAccessTokenGenerated, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
if tokenName == "" {
@@ -628,7 +628,7 @@ func (am *DefaultAccountManager) CreatePAT(ctx context.Context, accountID string
// DeletePAT deletes a specific PAT from a user
func (am *DefaultAccountManager) DeletePAT(ctx context.Context, accountID string, initiatorUserID string, targetUserID string, tokenID string) error {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -678,7 +678,7 @@ func (am *DefaultAccountManager) DeletePAT(ctx context.Context, accountID string
// GetPAT returns a specific PAT from a user
func (am *DefaultAccountManager) GetPAT(ctx context.Context, accountID string, initiatorUserID string, targetUserID string, tokenID string) (*PersonalAccessToken, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -710,7 +710,7 @@ func (am *DefaultAccountManager) GetPAT(ctx context.Context, accountID string, i
// GetAllPATs returns all PATs for a user
func (am *DefaultAccountManager) GetAllPATs(ctx context.Context, accountID string, initiatorUserID string, targetUserID string) ([]*PersonalAccessToken, error) {
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
account, err := am.Store.GetAccount(ctx, accountID)
@@ -752,7 +752,7 @@ func (am *DefaultAccountManager) SaveOrAddUser(ctx context.Context, accountID, i
return nil, status.Errorf(status.InvalidArgument, "provided user update is nil")
}
- unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
+ unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
defer unlock()
updatedUsers, err := am.SaveOrAddUsers(ctx, accountID, initiatorUserID, []*User{update}, addIfNotExists)
From c832cef44c2c588c2008dc637fb369d8b4ed6720 Mon Sep 17 00:00:00 2001
From: Bethuel Mmbaga
Date: Wed, 31 Jul 2024 19:48:12 +0300
Subject: [PATCH 41/77] Update SaveUsers and SaveGroups to SaveAccount (#2362)
Changed SaveUsers and SaveGroups method calls to SaveAccount for consistency in data persistence operations.
---
management/server/group.go | 2 +-
management/server/user.go | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/management/server/group.go b/management/server/group.go
index d7711c1268b..37a6fc305fb 100644
--- a/management/server/group.go
+++ b/management/server/group.go
@@ -163,7 +163,7 @@ func (am *DefaultAccountManager) SaveGroups(ctx context.Context, accountID, user
}
account.Network.IncSerial()
- if err = am.Store.SaveGroups(account.Id, account.Groups); err != nil {
+ if err = am.Store.SaveAccount(ctx, account); err != nil {
return err
}
diff --git a/management/server/user.go b/management/server/user.go
index 2cb6b45aaaa..b8afcda3ac7 100644
--- a/management/server/user.go
+++ b/management/server/user.go
@@ -859,7 +859,7 @@ func (am *DefaultAccountManager) SaveOrAddUsers(ctx context.Context, accountID,
}
account.Network.IncSerial()
- if err = am.Store.SaveUsers(account.Id, account.Users); err != nil {
+ if err = am.Store.SaveAccount(ctx, account); err != nil {
return nil, err
}
From 5ee9c77e907d3dfbf1f5886d0cf4be37eb09b7dd Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Wed, 31 Jul 2024 21:51:45 +0200
Subject: [PATCH 42/77] Move write peer lock (#2364)
Moved the write peer lock to avoid latency caused by disk access
Updated the method CancelPeerRoutines to use the peer public key
---
management/server/account.go | 24 ++++++++++++-------
management/server/grpcserver.go | 2 +-
management/server/mock_server/account_mock.go | 2 +-
3 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/management/server/account.go b/management/server/account.go
index b2b23dcb9a6..4648c00cdd6 100644
--- a/management/server/account.go
+++ b/management/server/account.go
@@ -136,7 +136,7 @@ type AccountManager interface {
GroupValidation(ctx context.Context, accountId string, groups []string) (bool, error)
GetValidatedPeers(account *Account) (map[string]struct{}, error)
SyncAndMarkPeer(ctx context.Context, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP) (*nbpeer.Peer, *NetworkMap, []*posture.Checks, error)
- CancelPeerRoutines(ctx context.Context, peer *nbpeer.Peer) error
+ CancelPeerRoutines(ctx context.Context, peerPubKey string) error
SyncPeerMeta(ctx context.Context, peerPubKey string, meta nbpeer.PeerSystemMeta) error
FindExistingPostureCheck(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error)
GetAccountIDForPeerKey(ctx context.Context, peerKey string) (string, error)
@@ -1858,6 +1858,11 @@ func (am *DefaultAccountManager) getAccountWithAuthorizationClaims(ctx context.C
}
func (am *DefaultAccountManager) SyncAndMarkPeer(ctx context.Context, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP) (*nbpeer.Peer, *NetworkMap, []*posture.Checks, error) {
+ // acquiring peer write lock here is ok since we only modify peer information that is supplied by the
+ // peer itself which can't be modified by API, and it only happens after an account read lock is acquired
+ peerUnlock := am.Store.AcquireWriteLockByUID(ctx, peerPubKey)
+ defer peerUnlock()
+
accountID, err := am.Store.GetAccountIDByPeerPubKey(ctx, peerPubKey)
if err != nil {
if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound {
@@ -1868,8 +1873,6 @@ func (am *DefaultAccountManager) SyncAndMarkPeer(ctx context.Context, peerPubKey
accountUnlock := am.Store.AcquireReadLockByUID(ctx, accountID)
defer accountUnlock()
- peerUnlock := am.Store.AcquireWriteLockByUID(ctx, peerPubKey)
- defer peerUnlock()
account, err := am.Store.GetAccount(ctx, accountID)
if err != nil {
@@ -1889,8 +1892,13 @@ func (am *DefaultAccountManager) SyncAndMarkPeer(ctx context.Context, peerPubKey
return peer, netMap, postureChecks, nil
}
-func (am *DefaultAccountManager) CancelPeerRoutines(ctx context.Context, peer *nbpeer.Peer) error {
- accountID, err := am.Store.GetAccountIDByPeerPubKey(ctx, peer.Key)
+func (am *DefaultAccountManager) CancelPeerRoutines(ctx context.Context, peerPubKey string) error {
+ // acquiring peer write lock here is ok since we only modify peer information that is supplied by the
+ // peer itself which can't be modified by API, and it only happens after an account read lock is acquired
+ peerUnlock := am.Store.AcquireWriteLockByUID(ctx, peerPubKey)
+ defer peerUnlock()
+
+ accountID, err := am.Store.GetAccountIDByPeerPubKey(ctx, peerPubKey)
if err != nil {
if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound {
return status.Errorf(status.Unauthenticated, "peer not registered")
@@ -1900,17 +1908,15 @@ func (am *DefaultAccountManager) CancelPeerRoutines(ctx context.Context, peer *n
accountUnlock := am.Store.AcquireReadLockByUID(ctx, accountID)
defer accountUnlock()
- peerUnlock := am.Store.AcquireWriteLockByUID(ctx, peer.Key)
- defer peerUnlock()
account, err := am.Store.GetAccount(ctx, accountID)
if err != nil {
return err
}
- err = am.MarkPeerConnected(ctx, peer.Key, false, nil, account)
+ err = am.MarkPeerConnected(ctx, peerPubKey, false, nil, account)
if err != nil {
- log.WithContext(ctx).Warnf("failed marking peer as connected %s %v", peer.Key, err)
+ log.WithContext(ctx).Warnf("failed marking peer as connected %s %v", peerPubKey, err)
}
return nil
diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go
index 170e72dd0e6..4a12a5c3eda 100644
--- a/management/server/grpcserver.go
+++ b/management/server/grpcserver.go
@@ -236,7 +236,7 @@ func (s *GRPCServer) sendUpdate(ctx context.Context, peerKey wgtypes.Key, peer *
func (s *GRPCServer) cancelPeerRoutines(ctx context.Context, peer *nbpeer.Peer) {
s.peersUpdateManager.CloseChannel(ctx, peer.ID)
s.turnCredentialsManager.CancelRefresh(peer.ID)
- _ = s.accountManager.CancelPeerRoutines(ctx, peer)
+ _ = s.accountManager.CancelPeerRoutines(ctx, peer.Key)
s.ephemeralManager.OnPeerDisconnected(ctx, peer)
}
diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go
index 25bcdfcee71..1adf9a2d63d 100644
--- a/management/server/mock_server/account_mock.go
+++ b/management/server/mock_server/account_mock.go
@@ -112,7 +112,7 @@ func (am *MockAccountManager) SyncAndMarkPeer(ctx context.Context, peerPubKey st
return nil, nil, nil, status.Errorf(codes.Unimplemented, "method MarkPeerConnected is not implemented")
}
-func (am *MockAccountManager) CancelPeerRoutines(_ context.Context, peer *nbpeer.Peer) error {
+func (am *MockAccountManager) CancelPeerRoutines(_ context.Context, peerPubKey string) error {
// TODO implement me
panic("implement me")
}
From 02f3105e480dddea1e3ac4085737ad7fc41459b0 Mon Sep 17 00:00:00 2001
From: Evgenii
Date: Thu, 1 Aug 2024 10:56:18 +0100
Subject: [PATCH 43/77] Freebsd test all root component (#2361)
* chore(tests): add all root component into FreeBSD check
* change timeout for each component
* add client tests execution measure
* revert -p1 for client tests and explain why
* measure duration of all test run
---
.github/workflows/golang-test-freebsd.yml | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/golang-test-freebsd.yml b/.github/workflows/golang-test-freebsd.yml
index 6cb5f7427fa..4f13ee30e63 100644
--- a/.github/workflows/golang-test-freebsd.yml
+++ b/.github/workflows/golang-test-freebsd.yml
@@ -30,6 +30,17 @@ jobs:
# -e - to faile on first error
run: |
set -e -x
- go build -o netbird client/main.go
- go test -timeout 5m -p 1 -failfast ./iface/...
- go test -timeout 5m -p 1 -failfast ./client/...
+ time go build -o netbird client/main.go
+ # check all component except management, since we do not support management server on freebsd
+ time go test -timeout 1m -failfast ./base62/...
+ # NOTE: without -p1 `client/internal/dns` will fail becasue of `listen udp4 :33100: bind: address already in use`
+ time go test -timeout 8m -failfast -p 1 ./client/...
+ time go test -timeout 1m -failfast ./dns/...
+ time go test -timeout 1m -failfast ./encryption/...
+ time go test -timeout 1m -failfast ./formatter/...
+ time go test -timeout 1m -failfast ./iface/...
+ time go test -timeout 1m -failfast ./route/...
+ time go test -timeout 1m -failfast ./sharedsock/...
+ time go test -timeout 1m -failfast ./signal/...
+ time go test -timeout 1m -failfast ./util/...
+ time go test -timeout 1m -failfast ./version/...
From cbf9f2058e1410d9a1e32ca5a6e63c914b8cb590 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Thu, 1 Aug 2024 16:21:43 +0200
Subject: [PATCH 44/77] Use accountID retrieved from the sync call to acquire
read lock sooner (#2369)
Use accountID retrieved from the sync call to acquire read lock sooner and avoiding extra DB calls.
- Use the account ID across sync calls
- Moved account read lock
- Renamed CancelPeerRoutines to OnPeerDisconnected
- Added race tests
---
management/server/account.go | 38 +---
management/server/grpcserver.go | 22 +--
management/server/management_proto_test.go | 183 +++++++++++++++++-
management/server/mock_server/account_mock.go | 8 +-
4 files changed, 198 insertions(+), 53 deletions(-)
diff --git a/management/server/account.go b/management/server/account.go
index 4648c00cdd6..5d3ee6dc109 100644
--- a/management/server/account.go
+++ b/management/server/account.go
@@ -135,8 +135,8 @@ type AccountManager interface {
UpdateIntegratedValidatorGroups(ctx context.Context, accountID string, userID string, groups []string) error
GroupValidation(ctx context.Context, accountId string, groups []string) (bool, error)
GetValidatedPeers(account *Account) (map[string]struct{}, error)
- SyncAndMarkPeer(ctx context.Context, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP) (*nbpeer.Peer, *NetworkMap, []*posture.Checks, error)
- CancelPeerRoutines(ctx context.Context, peerPubKey string) error
+ SyncAndMarkPeer(ctx context.Context, accountID string, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP) (*nbpeer.Peer, *NetworkMap, []*posture.Checks, error)
+ OnPeerDisconnected(ctx context.Context, accountID string, peerPubKey string) error
SyncPeerMeta(ctx context.Context, peerPubKey string, meta nbpeer.PeerSystemMeta) error
FindExistingPostureCheck(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error)
GetAccountIDForPeerKey(ctx context.Context, peerKey string) (string, error)
@@ -1857,22 +1857,11 @@ func (am *DefaultAccountManager) getAccountWithAuthorizationClaims(ctx context.C
}
}
-func (am *DefaultAccountManager) SyncAndMarkPeer(ctx context.Context, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP) (*nbpeer.Peer, *NetworkMap, []*posture.Checks, error) {
- // acquiring peer write lock here is ok since we only modify peer information that is supplied by the
- // peer itself which can't be modified by API, and it only happens after an account read lock is acquired
- peerUnlock := am.Store.AcquireWriteLockByUID(ctx, peerPubKey)
- defer peerUnlock()
-
- accountID, err := am.Store.GetAccountIDByPeerPubKey(ctx, peerPubKey)
- if err != nil {
- if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound {
- return nil, nil, nil, status.Errorf(status.Unauthenticated, "peer not registered")
- }
- return nil, nil, nil, err
- }
-
+func (am *DefaultAccountManager) SyncAndMarkPeer(ctx context.Context, accountID string, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP) (*nbpeer.Peer, *NetworkMap, []*posture.Checks, error) {
accountUnlock := am.Store.AcquireReadLockByUID(ctx, accountID)
defer accountUnlock()
+ peerUnlock := am.Store.AcquireWriteLockByUID(ctx, peerPubKey)
+ defer peerUnlock()
account, err := am.Store.GetAccount(ctx, accountID)
if err != nil {
@@ -1892,22 +1881,11 @@ func (am *DefaultAccountManager) SyncAndMarkPeer(ctx context.Context, peerPubKey
return peer, netMap, postureChecks, nil
}
-func (am *DefaultAccountManager) CancelPeerRoutines(ctx context.Context, peerPubKey string) error {
- // acquiring peer write lock here is ok since we only modify peer information that is supplied by the
- // peer itself which can't be modified by API, and it only happens after an account read lock is acquired
- peerUnlock := am.Store.AcquireWriteLockByUID(ctx, peerPubKey)
- defer peerUnlock()
-
- accountID, err := am.Store.GetAccountIDByPeerPubKey(ctx, peerPubKey)
- if err != nil {
- if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound {
- return status.Errorf(status.Unauthenticated, "peer not registered")
- }
- return err
- }
-
+func (am *DefaultAccountManager) OnPeerDisconnected(ctx context.Context, accountID string, peerPubKey string) error {
accountUnlock := am.Store.AcquireReadLockByUID(ctx, accountID)
defer accountUnlock()
+ peerUnlock := am.Store.AcquireWriteLockByUID(ctx, peerPubKey)
+ defer peerUnlock()
account, err := am.Store.GetAccount(ctx, accountID)
if err != nil {
diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go
index 4a12a5c3eda..f71a45d9992 100644
--- a/management/server/grpcserver.go
+++ b/management/server/grpcserver.go
@@ -156,7 +156,7 @@ func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementServi
log.WithContext(ctx).Tracef("peer system meta has to be provided on sync. Peer %s, remote addr %s", peerKey.String(), realIP)
}
- peer, netMap, postureChecks, err := s.accountManager.SyncAndMarkPeer(ctx, peerKey.String(), extractPeerMeta(ctx, syncReq.GetMeta()), realIP)
+ peer, netMap, postureChecks, err := s.accountManager.SyncAndMarkPeer(ctx, accountID, peerKey.String(), extractPeerMeta(ctx, syncReq.GetMeta()), realIP)
if err != nil {
return mapError(ctx, err)
}
@@ -179,11 +179,11 @@ func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementServi
s.appMetrics.GRPCMetrics().CountSyncRequestDuration(time.Since(reqStart))
}
- return s.handleUpdates(ctx, peerKey, peer, updates, srv)
+ return s.handleUpdates(ctx, accountID, peerKey, peer, updates, srv)
}
// handleUpdates sends updates to the connected peer until the updates channel is closed.
-func (s *GRPCServer) handleUpdates(ctx context.Context, peerKey wgtypes.Key, peer *nbpeer.Peer, updates chan *UpdateMessage, srv proto.ManagementService_SyncServer) error {
+func (s *GRPCServer) handleUpdates(ctx context.Context, accountID string, peerKey wgtypes.Key, peer *nbpeer.Peer, updates chan *UpdateMessage, srv proto.ManagementService_SyncServer) error {
for {
select {
// condition when there are some updates
@@ -194,12 +194,12 @@ func (s *GRPCServer) handleUpdates(ctx context.Context, peerKey wgtypes.Key, pee
if !open {
log.WithContext(ctx).Debugf("updates channel for peer %s was closed", peerKey.String())
- s.cancelPeerRoutines(ctx, peer)
+ s.cancelPeerRoutines(ctx, accountID, peer)
return nil
}
log.WithContext(ctx).Debugf("received an update for peer %s", peerKey.String())
- if err := s.sendUpdate(ctx, peerKey, peer, update, srv); err != nil {
+ if err := s.sendUpdate(ctx, accountID, peerKey, peer, update, srv); err != nil {
return err
}
@@ -207,7 +207,7 @@ func (s *GRPCServer) handleUpdates(ctx context.Context, peerKey wgtypes.Key, pee
case <-srv.Context().Done():
// happens when connection drops, e.g. client disconnects
log.WithContext(ctx).Debugf("stream of peer %s has been closed", peerKey.String())
- s.cancelPeerRoutines(ctx, peer)
+ s.cancelPeerRoutines(ctx, accountID, peer)
return srv.Context().Err()
}
}
@@ -215,10 +215,10 @@ func (s *GRPCServer) handleUpdates(ctx context.Context, peerKey wgtypes.Key, pee
// sendUpdate encrypts the update message using the peer key and the server's wireguard key,
// then sends the encrypted message to the connected peer via the sync server.
-func (s *GRPCServer) sendUpdate(ctx context.Context, peerKey wgtypes.Key, peer *nbpeer.Peer, update *UpdateMessage, srv proto.ManagementService_SyncServer) error {
+func (s *GRPCServer) sendUpdate(ctx context.Context, accountID string, peerKey wgtypes.Key, peer *nbpeer.Peer, update *UpdateMessage, srv proto.ManagementService_SyncServer) error {
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, update.Update)
if err != nil {
- s.cancelPeerRoutines(ctx, peer)
+ s.cancelPeerRoutines(ctx, accountID, peer)
return status.Errorf(codes.Internal, "failed processing update message")
}
err = srv.SendMsg(&proto.EncryptedMessage{
@@ -226,17 +226,17 @@ func (s *GRPCServer) sendUpdate(ctx context.Context, peerKey wgtypes.Key, peer *
Body: encryptedResp,
})
if err != nil {
- s.cancelPeerRoutines(ctx, peer)
+ s.cancelPeerRoutines(ctx, accountID, peer)
return status.Errorf(codes.Internal, "failed sending update message")
}
log.WithContext(ctx).Debugf("sent an update to peer %s", peerKey.String())
return nil
}
-func (s *GRPCServer) cancelPeerRoutines(ctx context.Context, peer *nbpeer.Peer) {
+func (s *GRPCServer) cancelPeerRoutines(ctx context.Context, accountID string, peer *nbpeer.Peer) {
s.peersUpdateManager.CloseChannel(ctx, peer.ID)
s.turnCredentialsManager.CancelRefresh(peer.ID)
- _ = s.accountManager.CancelPeerRoutines(ctx, peer.Key)
+ _ = s.accountManager.OnPeerDisconnected(ctx, accountID, peer.Key)
s.ephemeralManager.OnPeerDisconnected(ctx, peer)
}
diff --git a/management/server/management_proto_test.go b/management/server/management_proto_test.go
index e1f7787f216..2c9d43948f4 100644
--- a/management/server/management_proto_test.go
+++ b/management/server/management_proto_test.go
@@ -2,6 +2,7 @@ package server
import (
"context"
+ "fmt"
"net"
"os"
"path/filepath"
@@ -16,6 +17,7 @@ import (
"google.golang.org/grpc/keepalive"
"github.com/netbirdio/netbird/encryption"
+ "github.com/netbirdio/netbird/formatter"
mgmtProto "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/util"
@@ -83,7 +85,7 @@ func Test_SyncProtocol(t *testing.T) {
defer func() {
os.Remove(filepath.Join(dir, "store.json")) //nolint
}()
- mgmtServer, mgmtAddr, err := startManagement(t, &Config{
+ mgmtServer, _, mgmtAddr, err := startManagement(t, &Config{
Stuns: []*Host{{
Proto: "udp",
URI: "stun:stun.wiretrustee.com:3468",
@@ -399,32 +401,35 @@ func TestServer_GetDeviceAuthorizationFlow(t *testing.T) {
}
}
-func startManagement(t *testing.T, config *Config) (*grpc.Server, string, error) {
+func startManagement(t *testing.T, config *Config) (*grpc.Server, *DefaultAccountManager, string, error) {
t.Helper()
lis, err := net.Listen("tcp", "localhost:0")
if err != nil {
- return nil, "", err
+ return nil, nil, "", err
}
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
store, cleanUp, err := NewTestStoreFromJson(context.Background(), config.Datadir)
if err != nil {
- return nil, "", err
+ return nil, nil, "", err
}
t.Cleanup(cleanUp)
peersUpdateManager := NewPeersUpdateManager(nil)
eventStore := &activity.InMemoryEventStore{}
- accountManager, err := BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted",
+
+ ctx := context.WithValue(context.Background(), formatter.ExecutionContextKey, formatter.SystemSource) //nolint:staticcheck
+
+ accountManager, err := BuildManager(ctx, store, peersUpdateManager, nil, "", "netbird.selfhosted",
eventStore, nil, false, MocIntegratedValidator{})
if err != nil {
- return nil, "", err
+ return nil, nil, "", err
}
turnManager := NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
ephemeralMgr := NewEphemeralManager(store, accountManager)
mgmtServer, err := NewServer(context.Background(), config, accountManager, peersUpdateManager, turnManager, nil, ephemeralMgr)
if err != nil {
- return nil, "", err
+ return nil, nil, "", err
}
mgmtProto.RegisterManagementServiceServer(s, mgmtServer)
@@ -434,7 +439,7 @@ func startManagement(t *testing.T, config *Config) (*grpc.Server, string, error)
}
}()
- return s, lis.Addr().String(), nil
+ return s, accountManager, lis.Addr().String(), nil
}
func createRawClient(addr string) (mgmtProto.ManagementServiceClient, *grpc.ClientConn, error) {
@@ -454,3 +459,165 @@ func createRawClient(addr string) (mgmtProto.ManagementServiceClient, *grpc.Clie
return mgmtProto.NewManagementServiceClient(conn), conn, nil
}
+func Test_SyncStatusRace(t *testing.T) {
+ if os.Getenv("CI") == "true" && os.Getenv("NETBIRD_STORE_ENGINE") == "postgres" {
+ t.Skip("Skipping on CI and Postgres store")
+ }
+ for i := 0; i < 500; i++ {
+ t.Run(fmt.Sprintf("TestRun-%d", i), func(t *testing.T) {
+ testSyncStatusRace(t)
+ })
+ }
+}
+func testSyncStatusRace(t *testing.T) {
+ t.Helper()
+ dir := t.TempDir()
+ err := util.CopyFileContents("testdata/store_with_expired_peers.json", filepath.Join(dir, "store.json"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ os.Remove(filepath.Join(dir, "store.json")) //nolint
+ }()
+
+ mgmtServer, am, mgmtAddr, err := startManagement(t, &Config{
+ Stuns: []*Host{{
+ Proto: "udp",
+ URI: "stun:stun.wiretrustee.com:3468",
+ }},
+ TURNConfig: &TURNConfig{
+ TimeBasedCredentials: false,
+ CredentialsTTL: util.Duration{},
+ Secret: "whatever",
+ Turns: []*Host{{
+ Proto: "udp",
+ URI: "turn:stun.wiretrustee.com:3468",
+ }},
+ },
+ Signal: &Host{
+ Proto: "http",
+ URI: "signal.wiretrustee.com:10000",
+ },
+ Datadir: dir,
+ HttpConfig: nil,
+ })
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+ defer mgmtServer.GracefulStop()
+
+ client, clientConn, err := createRawClient(mgmtAddr)
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+
+ defer clientConn.Close()
+
+ // there are two peers already in the store, add two more
+ peers, err := registerPeers(2, client)
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+
+ serverKey, err := getServerKey(client)
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+
+ concurrentPeerKey2 := peers[1]
+ t.Log("Public key of concurrent peer: ", concurrentPeerKey2.PublicKey().String())
+
+ syncReq2 := &mgmtProto.SyncRequest{Meta: &mgmtProto.PeerSystemMeta{}}
+ message2, err := encryption.EncryptMessage(*serverKey, *concurrentPeerKey2, syncReq2)
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+
+ ctx2, cancelFunc2 := context.WithCancel(context.Background())
+
+ //client.
+ sync2, err := client.Sync(ctx2, &mgmtProto.EncryptedMessage{
+ WgPubKey: concurrentPeerKey2.PublicKey().String(),
+ Body: message2,
+ })
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+
+ resp2 := &mgmtProto.EncryptedMessage{}
+ err = sync2.RecvMsg(resp2)
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+
+ peerWithInvalidStatus := peers[0]
+ t.Log("Public key of peer with invalid status: ", peerWithInvalidStatus.PublicKey().String())
+
+ syncReq := &mgmtProto.SyncRequest{Meta: &mgmtProto.PeerSystemMeta{}}
+ message, err := encryption.EncryptMessage(*serverKey, *peerWithInvalidStatus, syncReq)
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+
+ ctx, cancelFunc := context.WithCancel(context.Background())
+
+ //client.
+ sync, err := client.Sync(ctx, &mgmtProto.EncryptedMessage{
+ WgPubKey: peerWithInvalidStatus.PublicKey().String(),
+ Body: message,
+ })
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+
+ // take the first registered peer as a base for the test. Total four.
+
+ resp := &mgmtProto.EncryptedMessage{}
+ err = sync.RecvMsg(resp)
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+
+ cancelFunc2()
+ time.Sleep(1 * time.Millisecond)
+ cancelFunc()
+ time.Sleep(10 * time.Millisecond)
+
+ ctx, cancelFunc = context.WithCancel(context.Background())
+ defer cancelFunc()
+ sync, err = client.Sync(ctx, &mgmtProto.EncryptedMessage{
+ WgPubKey: peerWithInvalidStatus.PublicKey().String(),
+ Body: message,
+ })
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+
+ resp = &mgmtProto.EncryptedMessage{}
+ err = sync.RecvMsg(resp)
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+
+ time.Sleep(10 * time.Millisecond)
+ peer, err := am.Store.GetPeerByPeerPubKey(context.Background(), peerWithInvalidStatus.PublicKey().String())
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+ if !peer.Status.Connected {
+ t.Fatal("Peer should be connected")
+ }
+}
diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go
index 1adf9a2d63d..a66bdee2b90 100644
--- a/management/server/mock_server/account_mock.go
+++ b/management/server/mock_server/account_mock.go
@@ -31,7 +31,7 @@ type MockAccountManager struct {
ListUsersFunc func(ctx context.Context, accountID string) ([]*server.User, error)
GetPeersFunc func(ctx context.Context, accountID, userID string) ([]*nbpeer.Peer, error)
MarkPeerConnectedFunc func(ctx context.Context, peerKey string, connected bool, realIP net.IP) error
- SyncAndMarkPeerFunc func(ctx context.Context, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP) (*nbpeer.Peer, *server.NetworkMap, []*posture.Checks, error)
+ SyncAndMarkPeerFunc func(ctx context.Context, accountID string, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP) (*nbpeer.Peer, *server.NetworkMap, []*posture.Checks, error)
DeletePeerFunc func(ctx context.Context, accountID, peerKey, userID string) error
GetNetworkMapFunc func(ctx context.Context, peerKey string) (*server.NetworkMap, error)
GetPeerNetworkFunc func(ctx context.Context, peerKey string) (*server.Network, error)
@@ -105,14 +105,14 @@ type MockAccountManager struct {
GetAccountIDForPeerKeyFunc func(ctx context.Context, peerKey string) (string, error)
}
-func (am *MockAccountManager) SyncAndMarkPeer(ctx context.Context, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP) (*nbpeer.Peer, *server.NetworkMap, []*posture.Checks, error) {
+func (am *MockAccountManager) SyncAndMarkPeer(ctx context.Context, accountID string, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP) (*nbpeer.Peer, *server.NetworkMap, []*posture.Checks, error) {
if am.SyncAndMarkPeerFunc != nil {
- return am.SyncAndMarkPeerFunc(ctx, peerPubKey, meta, realIP)
+ return am.SyncAndMarkPeerFunc(ctx, accountID, peerPubKey, meta, realIP)
}
return nil, nil, nil, status.Errorf(codes.Unimplemented, "method MarkPeerConnected is not implemented")
}
-func (am *MockAccountManager) CancelPeerRoutines(_ context.Context, peerPubKey string) error {
+func (am *MockAccountManager) OnPeerDisconnected(_ context.Context, accountID string, peerPubKey string) error {
// TODO implement me
panic("implement me")
}
From 0c8f8a62c75eca5f8d2e9496179e610f54f34a28 Mon Sep 17 00:00:00 2001
From: Zoltan Papp
Date: Thu, 1 Aug 2024 16:46:55 +0200
Subject: [PATCH 45/77] Handling invalid UTF-8 character in sys info (#2360)
In some operation systems, the sys info contains invalid characters.
In this patch try to keep the original fallback logic but filter out the cases when the character is invalid.
---
client/system/info_darwin_test.go | 5 +-
client/system/info_linux.go | 63 +++++++--
client/system/sysinfo_linux.go | 12 ++
client/system/sysinfo_linux_test.go | 198 ++++++++++++++++++++++++++++
encryption/message.go | 2 +-
5 files changed, 264 insertions(+), 16 deletions(-)
create mode 100644 client/system/sysinfo_linux.go
create mode 100644 client/system/sysinfo_linux_test.go
diff --git a/client/system/info_darwin_test.go b/client/system/info_darwin_test.go
index 94e0b9e5eba..5608bc77609 100644
--- a/client/system/info_darwin_test.go
+++ b/client/system/info_darwin_test.go
@@ -1,11 +1,12 @@
package system
import (
- log "github.com/sirupsen/logrus"
"testing"
+
+ log "github.com/sirupsen/logrus"
)
-func Test_sysInfo(t *testing.T) {
+func Test_sysInfoMac(t *testing.T) {
t.Skip("skipping darwin test")
serialNum, prodName, manufacturer := sysInfo()
if serialNum == "" {
diff --git a/client/system/info_linux.go b/client/system/info_linux.go
index db58d913fdb..b6a142bce28 100644
--- a/client/system/info_linux.go
+++ b/client/system/info_linux.go
@@ -21,6 +21,26 @@ import (
"github.com/netbirdio/netbird/version"
)
+type SysInfoGetter interface {
+ GetSysInfo() SysInfo
+}
+
+type SysInfoWrapper struct {
+ si sysinfo.SysInfo
+}
+
+func (s SysInfoWrapper) GetSysInfo() SysInfo {
+ s.si.GetSysInfo()
+ return SysInfo{
+ ChassisSerial: s.si.Chassis.Serial,
+ ProductSerial: s.si.Product.Serial,
+ BoardSerial: s.si.Board.Serial,
+ ProductName: s.si.Product.Name,
+ BoardName: s.si.Board.Name,
+ ProductVendor: s.si.Product.Vendor,
+ }
+}
+
// GetInfo retrieves and parses the system information
func GetInfo(ctx context.Context) *Info {
info := _getInfo()
@@ -45,7 +65,8 @@ func GetInfo(ctx context.Context) *Info {
log.Warnf("failed to discover network addresses: %s", err)
}
- serialNum, prodName, manufacturer := sysInfo()
+ si := SysInfoWrapper{}
+ serialNum, prodName, manufacturer := sysInfo(si.GetSysInfo())
env := Environment{
Cloud: detect_cloud.Detect(ctx),
@@ -87,20 +108,36 @@ func _getInfo() string {
return out.String()
}
-func sysInfo() (serialNumber string, productName string, manufacturer string) {
- var si sysinfo.SysInfo
- si.GetSysInfo()
+func sysInfo(si SysInfo) (string, string, string) {
isascii := regexp.MustCompile("^[[:ascii:]]+$")
- serial := si.Chassis.Serial
- if (serial == "Default string" || serial == "") && si.Product.Serial != "" {
- serial = si.Product.Serial
+
+ serials := []string{si.ChassisSerial, si.ProductSerial}
+ serial := ""
+
+ for _, s := range serials {
+ if isascii.MatchString(s) {
+ serial = s
+ if s != "Default string" {
+ break
+ }
+ }
+ }
+
+ if serial == "" && isascii.MatchString(si.BoardSerial) {
+ serial = si.BoardSerial
}
- if (!isascii.MatchString(serial)) && si.Board.Serial != "" {
- serial = si.Board.Serial
+
+ var name string
+ for _, n := range []string{si.ProductName, si.BoardName} {
+ if isascii.MatchString(n) {
+ name = n
+ break
+ }
}
- name := si.Product.Name
- if (!isascii.MatchString(name)) && si.Board.Name != "" {
- name = si.Board.Name
+
+ var manufacturer string
+ if isascii.MatchString(si.ProductVendor) {
+ manufacturer = si.ProductVendor
}
- return serial, name, si.Product.Vendor
+ return serial, name, manufacturer
}
diff --git a/client/system/sysinfo_linux.go b/client/system/sysinfo_linux.go
new file mode 100644
index 00000000000..df0f5574c2a
--- /dev/null
+++ b/client/system/sysinfo_linux.go
@@ -0,0 +1,12 @@
+package system
+
+// SysInfo used to moc out the sysinfo getter
+type SysInfo struct {
+ ChassisSerial string
+ ProductSerial string
+ BoardSerial string
+
+ ProductName string
+ BoardName string
+ ProductVendor string
+}
diff --git a/client/system/sysinfo_linux_test.go b/client/system/sysinfo_linux_test.go
new file mode 100644
index 00000000000..f6a0b70587b
--- /dev/null
+++ b/client/system/sysinfo_linux_test.go
@@ -0,0 +1,198 @@
+package system
+
+import "testing"
+
+func Test_sysInfo(t *testing.T) {
+ tests := []struct {
+ name string
+ sysInfo SysInfo
+ wantSerialNum string
+ wantProdName string
+ wantManufacturer string
+ }{
+ {
+ name: "Test Case 1",
+ sysInfo: SysInfo{
+ ChassisSerial: "Default string",
+ ProductSerial: "Default string",
+ BoardSerial: "M80-G8013200245",
+ ProductName: "B650M-HDV/M.2",
+ BoardName: "B650M-HDV/M.2",
+ ProductVendor: "ASRock",
+ },
+ wantSerialNum: "Default string",
+ wantProdName: "B650M-HDV/M.2",
+ wantManufacturer: "ASRock",
+ },
+ {
+ name: "Empty Chassis Serial",
+ sysInfo: SysInfo{
+ ChassisSerial: "",
+ ProductSerial: "Default string",
+ BoardSerial: "M80-G8013200245",
+ ProductName: "B650M-HDV/M.2",
+ BoardName: "B650M-HDV/M.2",
+ ProductVendor: "ASRock",
+ },
+ wantSerialNum: "Default string",
+ wantProdName: "B650M-HDV/M.2",
+ wantManufacturer: "ASRock",
+ },
+ {
+ name: "Empty Chassis Serial",
+ sysInfo: SysInfo{
+ ChassisSerial: "",
+ ProductSerial: "Default string",
+ BoardSerial: "M80-G8013200245",
+ ProductName: "B650M-HDV/M.2",
+ BoardName: "B650M-HDV/M.2",
+ ProductVendor: "ASRock",
+ },
+ wantSerialNum: "Default string",
+ wantProdName: "B650M-HDV/M.2",
+ wantManufacturer: "ASRock",
+ },
+ {
+ name: "Fallback to Product Serial",
+ sysInfo: SysInfo{
+ ChassisSerial: "Default string",
+ ProductSerial: "Product serial",
+ BoardSerial: "M80-G8013200245",
+ ProductName: "B650M-HDV/M.2",
+ BoardName: "B650M-HDV/M.2",
+ ProductVendor: "ASRock",
+ },
+ wantSerialNum: "Product serial",
+ wantProdName: "B650M-HDV/M.2",
+ wantManufacturer: "ASRock",
+ },
+ {
+ name: "Fallback to Product Serial with default string",
+ sysInfo: SysInfo{
+ ChassisSerial: "Default string",
+ ProductSerial: "Default string",
+ BoardSerial: "M80-G8013200245",
+ ProductName: "B650M-HDV/M.2",
+ BoardName: "B650M-HDV/M.2",
+ ProductVendor: "ASRock",
+ },
+ wantSerialNum: "Default string",
+ wantProdName: "B650M-HDV/M.2",
+ wantManufacturer: "ASRock",
+ },
+ {
+ name: "Non UTF-8 in Chassis Serial",
+ sysInfo: SysInfo{
+ ChassisSerial: "\x80",
+ ProductSerial: "Product serial",
+ BoardSerial: "M80-G8013200245",
+ ProductName: "B650M-HDV/M.2",
+ BoardName: "B650M-HDV/M.2",
+ ProductVendor: "ASRock",
+ },
+ wantSerialNum: "Product serial",
+ wantProdName: "B650M-HDV/M.2",
+ wantManufacturer: "ASRock",
+ },
+ {
+ name: "Non UTF-8 in Chassis Serial and Product Serial",
+ sysInfo: SysInfo{
+ ChassisSerial: "\x80",
+ ProductSerial: "\x80",
+ BoardSerial: "M80-G8013200245",
+ ProductName: "B650M-HDV/M.2",
+ BoardName: "B650M-HDV/M.2",
+ ProductVendor: "ASRock",
+ },
+ wantSerialNum: "M80-G8013200245",
+ wantProdName: "B650M-HDV/M.2",
+ wantManufacturer: "ASRock",
+ },
+ {
+ name: "Non UTF-8 in Chassis Serial and Product Serial and BoardSerial",
+ sysInfo: SysInfo{
+ ChassisSerial: "\x80",
+ ProductSerial: "\x80",
+ BoardSerial: "\x80",
+ ProductName: "B650M-HDV/M.2",
+ BoardName: "B650M-HDV/M.2",
+ ProductVendor: "ASRock",
+ },
+ wantSerialNum: "",
+ wantProdName: "B650M-HDV/M.2",
+ wantManufacturer: "ASRock",
+ },
+
+ {
+ name: "Empty Product Name",
+ sysInfo: SysInfo{
+ ChassisSerial: "Default string",
+ ProductSerial: "Default string",
+ BoardSerial: "M80-G8013200245",
+ ProductName: "",
+ BoardName: "boardname",
+ ProductVendor: "ASRock",
+ },
+ wantSerialNum: "Default string",
+ wantProdName: "boardname",
+ wantManufacturer: "ASRock",
+ },
+ {
+ name: "Invalid Product Name",
+ sysInfo: SysInfo{
+ ChassisSerial: "Default string",
+ ProductSerial: "Default string",
+ BoardSerial: "M80-G8013200245",
+ ProductName: "\x80",
+ BoardName: "boardname",
+ ProductVendor: "ASRock",
+ },
+ wantSerialNum: "Default string",
+ wantProdName: "boardname",
+ wantManufacturer: "ASRock",
+ },
+ {
+ name: "Invalid BoardName Name",
+ sysInfo: SysInfo{
+ ChassisSerial: "Default string",
+ ProductSerial: "Default string",
+ BoardSerial: "M80-G8013200245",
+ ProductName: "\x80",
+ BoardName: "\x80",
+ ProductVendor: "ASRock",
+ },
+ wantSerialNum: "Default string",
+ wantProdName: "",
+ wantManufacturer: "ASRock",
+ },
+ {
+ name: "Invalid chars",
+ sysInfo: SysInfo{
+ ChassisSerial: "\x80",
+ ProductSerial: "\x80",
+ BoardSerial: "\x80",
+ ProductName: "\x80",
+ BoardName: "\x80",
+ ProductVendor: "\x80",
+ },
+ wantSerialNum: "",
+ wantProdName: "",
+ wantManufacturer: "",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ gotSerialNum, gotProdName, gotManufacturer := sysInfo(tt.sysInfo)
+ if gotSerialNum != tt.wantSerialNum {
+ t.Errorf("sysInfo() gotSerialNum = %v, want %v", gotSerialNum, tt.wantSerialNum)
+ }
+ if gotProdName != tt.wantProdName {
+ t.Errorf("sysInfo() gotProdName = %v, want %v", gotProdName, tt.wantProdName)
+ }
+ if gotManufacturer != tt.wantManufacturer {
+ t.Errorf("sysInfo() gotManufacturer = %v, want %v", gotManufacturer, tt.wantManufacturer)
+ }
+ })
+ }
+}
diff --git a/encryption/message.go b/encryption/message.go
index a646fa67946..6e4cd739173 100644
--- a/encryption/message.go
+++ b/encryption/message.go
@@ -10,7 +10,7 @@ import (
func EncryptMessage(remotePubKey wgtypes.Key, ourPrivateKey wgtypes.Key, message pb.Message) ([]byte, error) {
byteResp, err := pb.Marshal(message)
if err != nil {
- log.Errorf("failed marshalling message %v", err)
+ log.Errorf("failed marshalling message %v, %+v", err, message.String())
return nil, err
}
From 3506ac4234f00ddced815f0d237bac08516ce770 Mon Sep 17 00:00:00 2001
From: Zoltan Papp
Date: Thu, 1 Aug 2024 17:13:58 +0200
Subject: [PATCH 46/77] When creating new setup key, "revoked" field doesn't do
anything (#2357)
Remove unused field from API
---
management/server/http/api/openapi.yml | 39 ++++++++++++++++++++++++-
management/server/http/api/types.gen.go | 23 ++++++++++++++-
2 files changed, 60 insertions(+), 2 deletions(-)
diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml
index 30cb19c0c02..45887dc2ece 100644
--- a/management/server/http/api/openapi.yml
+++ b/management/server/http/api/openapi.yml
@@ -526,6 +526,43 @@ components:
- revoked
- auto_groups
- usage_limit
+ CreateSetupKeyRequest:
+ type: object
+ properties:
+ name:
+ description: Setup Key name
+ type: string
+ example: Default key
+ type:
+ description: Setup key type, one-off for single time usage and reusable
+ type: string
+ example: reusable
+ expires_in:
+ description: Expiration time in seconds
+ type: integer
+ minimum: 86400
+ maximum: 31536000
+ example: 86400
+ auto_groups:
+ description: List of group IDs to auto-assign to peers registered with this key
+ type: array
+ items:
+ type: string
+ example: "ch8i4ug6lnn4g9hqv7m0"
+ usage_limit:
+ description: A number of times this key can be used. The value of 0 indicates the unlimited usage.
+ type: integer
+ example: 0
+ ephemeral:
+ description: Indicate that the peer will be ephemeral or not
+ type: boolean
+ example: true
+ required:
+ - name
+ - type
+ - expires_in
+ - auto_groups
+ - usage_limit
PersonalAccessToken:
type: object
properties:
@@ -1806,7 +1843,7 @@ paths:
content:
'application/json':
schema:
- $ref: '#/components/schemas/SetupKeyRequest'
+ $ref: '#/components/schemas/CreateSetupKeyRequest'
responses:
'200':
description: A Setup Keys Object
diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go
index f731356eed9..77a6c643d7a 100644
--- a/management/server/http/api/types.gen.go
+++ b/management/server/http/api/types.gen.go
@@ -254,6 +254,27 @@ type Country struct {
// CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country
type CountryCode = string
+// CreateSetupKeyRequest defines model for CreateSetupKeyRequest.
+type CreateSetupKeyRequest struct {
+ // AutoGroups List of group IDs to auto-assign to peers registered with this key
+ AutoGroups []string `json:"auto_groups"`
+
+ // Ephemeral Indicate that the peer will be ephemeral or not
+ Ephemeral *bool `json:"ephemeral,omitempty"`
+
+ // ExpiresIn Expiration time in seconds
+ ExpiresIn int `json:"expires_in"`
+
+ // Name Setup Key name
+ Name string `json:"name"`
+
+ // Type Setup key type, one-off for single time usage and reusable
+ Type string `json:"type"`
+
+ // UsageLimit A number of times this key can be used. The value of 0 indicates the unlimited usage.
+ UsageLimit int `json:"usage_limit"`
+}
+
// DNSSettings defines model for DNSSettings.
type DNSSettings struct {
// DisabledManagementGroups Groups whose DNS management is disabled
@@ -1241,7 +1262,7 @@ type PostApiRoutesJSONRequestBody = RouteRequest
type PutApiRoutesRouteIdJSONRequestBody = RouteRequest
// PostApiSetupKeysJSONRequestBody defines body for PostApiSetupKeys for application/json ContentType.
-type PostApiSetupKeysJSONRequestBody = SetupKeyRequest
+type PostApiSetupKeysJSONRequestBody = CreateSetupKeyRequest
// PutApiSetupKeysKeyIdJSONRequestBody defines body for PutApiSetupKeysKeyId for application/json ContentType.
type PutApiSetupKeysKeyIdJSONRequestBody = SetupKeyRequest
From df8b8db068c15c23a56cd650b5d9226d93d93149 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 1 Aug 2024 17:20:15 +0200
Subject: [PATCH 47/77] Bump github.com/docker/docker (#2356)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.1.3+incompatible to 26.1.4+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v26.1.3...v26.1.4)
---
updated-dependencies:
- dependency-name: github.com/docker/docker
dependency-type: indirect
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index 1da44da3bb4..7f242c2033a 100644
--- a/go.mod
+++ b/go.mod
@@ -115,7 +115,7 @@ require (
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect
- github.com/docker/docker v26.1.3+incompatible // indirect
+ github.com/docker/docker v26.1.4+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
diff --git a/go.sum b/go.sum
index 8423113444c..1251a6fd7ca 100644
--- a/go.sum
+++ b/go.sum
@@ -81,8 +81,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
-github.com/docker/docker v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo=
-github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU=
+github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
From 24e031ab74112e7dc8fb6733ed1fc38517545794 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20Ko=C5=82odziejczak?=
<31549762+mrl5@users.noreply.github.com>
Date: Thu, 1 Aug 2024 18:22:02 +0200
Subject: [PATCH 48/77] Fix syslog output containing duplicated timestamps
(#2292)
```console
journalctl
```
```diff
- Jul 19 14:41:01 rpi /usr/bin/netbird[614]: 2024-07-19T14:41:01+02:00 ERRO %!s(): error while handling message of Peer [key: REDACTED] error: [wrongly addressed message REDACTED]
- Jul 19 21:53:03 rpi /usr/bin/netbird[614]: 2024-07-19T21:53:03+02:00 WARN %!s(): disconnected from the Signal service but will retry silently. Reason: rpc error: code = Internal desc = server closed the stream without sending trailers
- Jul 19 21:53:04 rpi /usr/bin/netbird[614]: 2024-07-19T21:53:04+02:00 INFO %!s(): connected to the Signal Service stream
- Jul 19 22:24:10 rpi /usr/bin/netbird[614]: 2024-07-19T22:24:10+02:00 WARN [error: read udp 192.168.1.11:48398->9.9.9.9:53: i/o timeout, upstream: 9.9.9.9:53] %!s(): got an error while connecting to upstream
+ Jul 19 14:41:01 rpi /usr/bin/netbird[614]: error while handling message of Peer [key: REDACTED] error: [wrongly addressed message REDACTED]
+ Jul 19 21:53:03 rpi /usr/bin/netbird[614]: disconnected from the Signal service but will retry silently. Reason: rpc error: code = Internal desc = server closed the stream without sending trailers
+ Jul 19 21:53:04 rpi /usr/bin/netbird[614]: connected to the Signal Service stream
+ Jul 19 22:24:10 rpi /usr/bin/netbird[614]: [error: read udp 192.168.1.11:48398->9.9.9.9:53: i/o timeout, upstream: 9.9.9.9:53] got an error while connecting to upstream
```
please notice that although log level is no longer present in the syslog
message it is still respected by syslog logger, so the log levels are
not lost:
```console
journalctl -p 3
```
```diff
- Jul 19 14:41:01 rpi /usr/bin/netbird[614]: 2024-07-19T14:41:01+02:00 ERRO %!s(): error while handling message of Peer [key: REDACTED] error: [wrongly addressed message REDACTED]
+ Jul 19 14:41:01 rpi /usr/bin/netbird[614]: error while handling message of Peer [key: REDACTED] error: [wrongly addressed message REDACTED]
```
---
.editorconfig | 8 ++++++++
formatter/formatter.go | 34 +++++++++++++++++++++++++++++++++-
formatter/formatter_test.go | 19 ++++++++++++++++++-
formatter/set.go | 6 ++++++
util/log.go | 3 +++
5 files changed, 68 insertions(+), 2 deletions(-)
create mode 100644 .editorconfig
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000000..3dcb869d2fa
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,8 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+
+[*.go]
+indent_style = tab
diff --git a/formatter/formatter.go b/formatter/formatter.go
index a37c679149c..74de3860358 100644
--- a/formatter/formatter.go
+++ b/formatter/formatter.go
@@ -14,14 +14,29 @@ type TextFormatter struct {
levelDesc []string
}
+// SyslogFormatter formats logs into text
+type SyslogFormatter struct {
+ levelDesc []string
+}
+
+var validLevelDesc = []string{"PANC", "FATL", "ERRO", "WARN", "INFO", "DEBG", "TRAC"}
+
+
// NewTextFormatter create new MyTextFormatter instance
func NewTextFormatter() *TextFormatter {
return &TextFormatter{
- levelDesc: []string{"PANC", "FATL", "ERRO", "WARN", "INFO", "DEBG", "TRAC"},
+ levelDesc: validLevelDesc,
timestampFormat: time.RFC3339, // or RFC3339
}
}
+// NewSyslogFormatter create new MySyslogFormatter instance
+func NewSyslogFormatter() *SyslogFormatter {
+ return &SyslogFormatter{
+ levelDesc: validLevelDesc,
+ }
+}
+
// Format renders a single log entry
func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
var fields string
@@ -49,3 +64,20 @@ func (f *TextFormatter) parseLevel(level logrus.Level) string {
return f.levelDesc[level]
}
+
+// Format renders a single log entry
+func (f *SyslogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ var fields string
+ keys := make([]string, 0, len(entry.Data))
+ for k, v := range entry.Data {
+ if k == "source" {
+ continue
+ }
+ keys = append(keys, fmt.Sprintf("%s: %v", k, v))
+ }
+
+ if len(keys) > 0 {
+ fields = fmt.Sprintf("[%s] ", strings.Join(keys, ", "))
+ }
+ return []byte(fmt.Sprintf("%s%s\n", fields, entry.Message)), nil
+}
diff --git a/formatter/formatter_test.go b/formatter/formatter_test.go
index 54bc8a75639..1ed2079580f 100644
--- a/formatter/formatter_test.go
+++ b/formatter/formatter_test.go
@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestLogMessageFormat(t *testing.T) {
+func TestLogTextFormat(t *testing.T) {
someEntry := &logrus.Entry{
Data: logrus.Fields{"att1": 1, "att2": 2, "source": "some/fancy/path.go:46"},
@@ -24,3 +24,20 @@ func TestLogMessageFormat(t *testing.T) {
expectedString := "^2021-02-21T01:10:30Z WARN \\[(att1: 1, att2: 2|att2: 2, att1: 1)\\] some/fancy/path.go:46: Some Message\\s+$"
assert.Regexp(t, expectedString, parsedString)
}
+
+func TestLogSyslogFormat(t *testing.T) {
+
+ someEntry := &logrus.Entry{
+ Data: logrus.Fields{"att1": 1, "att2": 2, "source": "some/fancy/path.go:46"},
+ Time: time.Date(2021, time.Month(2), 21, 1, 10, 30, 0, time.UTC),
+ Level: 3,
+ Message: "Some Message",
+ }
+
+ formatter := NewSyslogFormatter()
+ result, _ := formatter.Format(someEntry)
+
+ parsedString := string(result)
+ expectedString := "^\\[(att1: 1, att2: 2|att2: 2, att1: 1)\\] Some Message\\s+$"
+ assert.Regexp(t, expectedString, parsedString)
+}
diff --git a/formatter/set.go b/formatter/set.go
index f9ccef6014a..9dfea5a7fd0 100644
--- a/formatter/set.go
+++ b/formatter/set.go
@@ -10,6 +10,12 @@ func SetTextFormatter(logger *logrus.Logger) {
logger.ReportCaller = true
logger.AddHook(NewContextHook())
}
+// SetSyslogFormatter set the text formatter for given logger.
+func SetSyslogFormatter(logger *logrus.Logger) {
+ logger.Formatter = NewSyslogFormatter()
+ logger.ReportCaller = true
+ logger.AddHook(NewContextHook())
+}
// SetJSONFormatter set the JSON formatter for given logger.
func SetJSONFormatter(logger *logrus.Logger) {
diff --git a/util/log.go b/util/log.go
index 74b99311e1a..4bce75e4adc 100644
--- a/util/log.go
+++ b/util/log.go
@@ -35,8 +35,11 @@ func InitLog(logLevel string, logPath string) error {
AddSyslogHook()
}
+ //nolint:gocritic
if os.Getenv("NB_LOG_FORMAT") == "json" {
formatter.SetJSONFormatter(log.StandardLogger())
+ } else if logPath == "syslog" {
+ formatter.SetSyslogFormatter(log.StandardLogger())
} else {
formatter.SetTextFormatter(log.StandardLogger())
}
From 57624203c94c686ee538f7e8f6d076967d6e8a6b Mon Sep 17 00:00:00 2001
From: Viktor Liu <17948409+lixmal@users.noreply.github.com>
Date: Thu, 1 Aug 2024 18:38:19 +0200
Subject: [PATCH 49/77] Allow route updates even if some domains failed
resolution (#2368)
---
client/internal/routemanager/dynamic/route.go | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/client/internal/routemanager/dynamic/route.go b/client/internal/routemanager/dynamic/route.go
index 8429b4534de..e957107980b 100644
--- a/client/internal/routemanager/dynamic/route.go
+++ b/client/internal/routemanager/dynamic/route.go
@@ -189,9 +189,14 @@ func (r *Route) startResolver(ctx context.Context) {
}
func (r *Route) update(ctx context.Context) error {
- if resolved, err := r.resolveDomains(); err != nil {
- return fmt.Errorf("resolve domains: %w", err)
- } else if err := r.updateDynamicRoutes(ctx, resolved); err != nil {
+ resolved, err := r.resolveDomains()
+ if err != nil {
+ if len(resolved) == 0 {
+ return fmt.Errorf("resolve domains: %w", err)
+ }
+ log.Warnf("Failed to resolve domains: %v", err)
+ }
+ if err := r.updateDynamicRoutes(ctx, resolved); err != nil {
return fmt.Errorf("update dynamic routes: %w", err)
}
From 216d9f2ee8bd0c7cbcd6e0bf3c4318aa8785c10e Mon Sep 17 00:00:00 2001
From: keacwu
Date: Fri, 2 Aug 2024 00:52:38 +0800
Subject: [PATCH 50/77] Adding geolocation download log message. (#2085)
* Adding geolocation download prompt message.
* import log file and remove unnecessary else
---------
Co-authored-by: Maycon Santos
---
management/server/geolocation/database.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/management/server/geolocation/database.go b/management/server/geolocation/database.go
index 1bada60758d..c9b2eafffdd 100644
--- a/management/server/geolocation/database.go
+++ b/management/server/geolocation/database.go
@@ -9,6 +9,7 @@ import (
"path"
"strconv"
+ log "github.com/sirupsen/logrus"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
@@ -30,6 +31,8 @@ func loadGeolocationDatabases(dataDir string) error {
continue
}
+ log.Infof("geo location file %s not found , file will be downloaded", file)
+
switch file {
case MMDBFileName:
extractFunc := func(src string, dst string) error {
From f84b606506f6082581e4bb1983dd31bb1f9ffbd9 Mon Sep 17 00:00:00 2001
From: David Fry
Date: Thu, 1 Aug 2024 18:52:50 +0200
Subject: [PATCH 51/77] add extra auth audience (#2350)
---
management/server/config.go | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/management/server/config.go b/management/server/config.go
index 3e5ff1eaf9b..4efe4fe7469 100644
--- a/management/server/config.go
+++ b/management/server/config.go
@@ -56,6 +56,10 @@ type Config struct {
func (c Config) GetAuthAudiences() []string {
audiences := []string{c.HttpConfig.AuthAudience}
+ if c.HttpConfig.ExtraAuthAudience != "" {
+ audiences = append(audiences, c.HttpConfig.ExtraAuthAudience)
+ }
+
if c.DeviceAuthorizationFlow != nil && c.DeviceAuthorizationFlow.ProviderConfig.Audience != "" {
audiences = append(audiences, c.DeviceAuthorizationFlow.ProviderConfig.Audience)
}
@@ -90,6 +94,8 @@ type HttpServerConfig struct {
OIDCConfigEndpoint string
// IdpSignKeyRefreshEnabled identifies the signing key is currently being rotated or not
IdpSignKeyRefreshEnabled bool
+ // Extra audience
+ ExtraAuthAudience string
}
// Host represents a Wiretrustee host (e.g. STUN, TURN, Signal)
From 5ad4ae769a9e9629b0ed52e2f8eeec0f03812639 Mon Sep 17 00:00:00 2001
From: Viktor Liu <17948409+lixmal@users.noreply.github.com>
Date: Fri, 2 Aug 2024 11:47:12 +0200
Subject: [PATCH 52/77] Extend client debug bundle (#2341)
Adds readme (with --anonymize)
Fixes archive file timestamps
Adds routes info
Adds interfaces
Adds client config
---
client/anonymize/anonymize.go | 15 +
client/cmd/debug.go | 31 +-
client/cmd/root.go | 4 +
client/cmd/status.go | 17 +-
.../routemanager/systemops/systemops_bsd.go | 2 +-
.../systemops/systemops_generic.go | 4 +-
.../routemanager/systemops/systemops_linux.go | 4 +-
.../systemops/systemops_nonlinux.go | 2 +-
.../systemops/systemops_windows.go | 2 +-
client/proto/daemon.pb.go | 18 +-
client/proto/daemon.proto | 1 +
client/server/debug.go | 397 ++++++++++++++++--
12 files changed, 418 insertions(+), 79 deletions(-)
diff --git a/client/anonymize/anonymize.go b/client/anonymize/anonymize.go
index acbd0441e1c..208e74d53a5 100644
--- a/client/anonymize/anonymize.go
+++ b/client/anonymize/anonymize.go
@@ -178,6 +178,21 @@ func (a *Anonymizer) AnonymizeDNSLogLine(logEntry string) string {
})
}
+// AnonymizeRoute anonymizes a route string by replacing IP addresses with anonymized versions and
+// domain names with random strings.
+func (a *Anonymizer) AnonymizeRoute(route string) string {
+ prefix, err := netip.ParsePrefix(route)
+ if err == nil {
+ ip := a.AnonymizeIPString(prefix.Addr().String())
+ return fmt.Sprintf("%s/%d", ip, prefix.Bits())
+ }
+ domains := strings.Split(route, ", ")
+ for i, domain := range domains {
+ domains[i] = a.AnonymizeDomain(domain)
+ }
+ return strings.Join(domains, ", ")
+}
+
func isWellKnown(addr netip.Addr) bool {
wellKnown := []string{
"8.8.8.8", "8.8.4.4", // Google DNS IPv4
diff --git a/client/cmd/debug.go b/client/cmd/debug.go
index da5e0945af7..dbdce91ab29 100644
--- a/client/cmd/debug.go
+++ b/client/cmd/debug.go
@@ -5,6 +5,7 @@ import (
"fmt"
"time"
+ log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"google.golang.org/grpc/status"
@@ -13,6 +14,8 @@ import (
"github.com/netbirdio/netbird/client/server"
)
+const errCloseConnection = "Failed to close connection: %v"
+
var debugCmd = &cobra.Command{
Use: "debug",
Short: "Debugging commands",
@@ -63,12 +66,17 @@ func debugBundle(cmd *cobra.Command, _ []string) error {
if err != nil {
return err
}
- defer conn.Close()
+ defer func() {
+ if err := conn.Close(); err != nil {
+ log.Errorf(errCloseConnection, err)
+ }
+ }()
client := proto.NewDaemonServiceClient(conn)
resp, err := client.DebugBundle(cmd.Context(), &proto.DebugBundleRequest{
- Anonymize: anonymizeFlag,
- Status: getStatusOutput(cmd),
+ Anonymize: anonymizeFlag,
+ Status: getStatusOutput(cmd),
+ SystemInfo: debugSystemInfoFlag,
})
if err != nil {
return fmt.Errorf("failed to bundle debug: %v", status.Convert(err).Message())
@@ -84,7 +92,11 @@ func setLogLevel(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
- defer conn.Close()
+ defer func() {
+ if err := conn.Close(); err != nil {
+ log.Errorf(errCloseConnection, err)
+ }
+ }()
client := proto.NewDaemonServiceClient(conn)
level := server.ParseLogLevel(args[0])
@@ -113,7 +125,11 @@ func runForDuration(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
- defer conn.Close()
+ defer func() {
+ if err := conn.Close(); err != nil {
+ log.Errorf(errCloseConnection, err)
+ }
+ }()
client := proto.NewDaemonServiceClient(conn)
@@ -189,8 +205,9 @@ func runForDuration(cmd *cobra.Command, args []string) error {
cmd.Println("Creating debug bundle...")
resp, err := client.DebugBundle(cmd.Context(), &proto.DebugBundleRequest{
- Anonymize: anonymizeFlag,
- Status: statusOutput,
+ Anonymize: anonymizeFlag,
+ Status: statusOutput,
+ SystemInfo: debugSystemInfoFlag,
})
if err != nil {
return fmt.Errorf("failed to bundle debug: %v", status.Convert(err).Message())
diff --git a/client/cmd/root.go b/client/cmd/root.go
index 1e5c56366f2..010ffb25a60 100644
--- a/client/cmd/root.go
+++ b/client/cmd/root.go
@@ -37,6 +37,7 @@ const (
serverSSHAllowedFlag = "allow-server-ssh"
extraIFaceBlackListFlag = "extra-iface-blacklist"
dnsRouteIntervalFlag = "dns-router-interval"
+ systemInfoFlag = "system-info"
)
var (
@@ -69,6 +70,7 @@ var (
autoConnectDisabled bool
extraIFaceBlackList []string
anonymizeFlag bool
+ debugSystemInfoFlag bool
dnsRouteInterval time.Duration
rootCmd = &cobra.Command{
@@ -165,6 +167,8 @@ func init() {
upCmd.PersistentFlags().BoolVar(&rosenpassPermissive, rosenpassPermissiveFlag, false, "[Experimental] Enable Rosenpass in permissive mode to allow this peer to accept WireGuard connections without requiring Rosenpass functionality from peers that do not have Rosenpass enabled.")
upCmd.PersistentFlags().BoolVar(&serverSSHAllowed, serverSSHAllowedFlag, false, "Allow SSH server on peer. If enabled, the SSH server will be permitted")
upCmd.PersistentFlags().BoolVar(&autoConnectDisabled, disableAutoConnectFlag, false, "Disables auto-connect feature. If enabled, then the client won't connect automatically when the service starts.")
+
+ debugCmd.PersistentFlags().BoolVarP(&debugSystemInfoFlag, systemInfoFlag, "S", false, "Adds system information to the debug bundle")
}
// SetupCloseHandler handles SIGTERM signal and exits with success
diff --git a/client/cmd/status.go b/client/cmd/status.go
index e6c7b8be8ec..d9b7a9c91c9 100644
--- a/client/cmd/status.go
+++ b/client/cmd/status.go
@@ -807,7 +807,7 @@ func anonymizePeerDetail(a *anonymize.Anonymizer, peer *peerStateDetailOutput) {
}
for i, route := range peer.Routes {
- peer.Routes[i] = anonymizeRoute(a, route)
+ peer.Routes[i] = a.AnonymizeRoute(route)
}
}
@@ -843,21 +843,8 @@ func anonymizeOverview(a *anonymize.Anonymizer, overview *statusOutputOverview)
}
for i, route := range overview.Routes {
- overview.Routes[i] = anonymizeRoute(a, route)
+ overview.Routes[i] = a.AnonymizeRoute(route)
}
overview.FQDN = a.AnonymizeDomain(overview.FQDN)
}
-
-func anonymizeRoute(a *anonymize.Anonymizer, route string) string {
- prefix, err := netip.ParsePrefix(route)
- if err == nil {
- ip := a.AnonymizeIPString(prefix.Addr().String())
- return fmt.Sprintf("%s/%d", ip, prefix.Bits())
- }
- domains := strings.Split(route, ", ")
- for i, domain := range domains {
- domains[i] = a.AnonymizeDomain(domain)
- }
- return strings.Join(domains, ", ")
-}
diff --git a/client/internal/routemanager/systemops/systemops_bsd.go b/client/internal/routemanager/systemops/systemops_bsd.go
index b7fb554db2c..5e3b20a860e 100644
--- a/client/internal/routemanager/systemops/systemops_bsd.go
+++ b/client/internal/routemanager/systemops/systemops_bsd.go
@@ -22,7 +22,7 @@ type Route struct {
Interface *net.Interface
}
-func getRoutesFromTable() ([]netip.Prefix, error) {
+func GetRoutesFromTable() ([]netip.Prefix, error) {
tab, err := retryFetchRIB()
if err != nil {
return nil, fmt.Errorf("fetch RIB: %v", err)
diff --git a/client/internal/routemanager/systemops/systemops_generic.go b/client/internal/routemanager/systemops/systemops_generic.go
index 615e1b528b4..671545b865f 100644
--- a/client/internal/routemanager/systemops/systemops_generic.go
+++ b/client/internal/routemanager/systemops/systemops_generic.go
@@ -427,7 +427,7 @@ func ipToAddr(ip net.IP, intf *net.Interface) (netip.Addr, error) {
}
func existsInRouteTable(prefix netip.Prefix) (bool, error) {
- routes, err := getRoutesFromTable()
+ routes, err := GetRoutesFromTable()
if err != nil {
return false, fmt.Errorf("get routes from table: %w", err)
}
@@ -440,7 +440,7 @@ func existsInRouteTable(prefix netip.Prefix) (bool, error) {
}
func isSubRange(prefix netip.Prefix) (bool, error) {
- routes, err := getRoutesFromTable()
+ routes, err := GetRoutesFromTable()
if err != nil {
return false, fmt.Errorf("get routes from table: %w", err)
}
diff --git a/client/internal/routemanager/systemops/systemops_linux.go b/client/internal/routemanager/systemops/systemops_linux.go
index c4f69fba5c0..2d0c5782697 100644
--- a/client/internal/routemanager/systemops/systemops_linux.go
+++ b/client/internal/routemanager/systemops/systemops_linux.go
@@ -206,7 +206,7 @@ func (r *SysOps) RemoveVPNRoute(prefix netip.Prefix, intf *net.Interface) error
return nil
}
-func getRoutesFromTable() ([]netip.Prefix, error) {
+func GetRoutesFromTable() ([]netip.Prefix, error) {
v4Routes, err := getRoutes(syscall.RT_TABLE_MAIN, netlink.FAMILY_V4)
if err != nil {
return nil, fmt.Errorf("get v4 routes: %w", err)
@@ -504,7 +504,7 @@ func getAddressFamily(prefix netip.Prefix) int {
func hasSeparateRouting() ([]netip.Prefix, error) {
if isLegacy() {
- return getRoutesFromTable()
+ return GetRoutesFromTable()
}
return nil, ErrRoutingIsSeparate
}
diff --git a/client/internal/routemanager/systemops/systemops_nonlinux.go b/client/internal/routemanager/systemops/systemops_nonlinux.go
index 0adeb09927c..3b52fc7af6e 100644
--- a/client/internal/routemanager/systemops/systemops_nonlinux.go
+++ b/client/internal/routemanager/systemops/systemops_nonlinux.go
@@ -24,5 +24,5 @@ func EnableIPForwarding() error {
}
func hasSeparateRouting() ([]netip.Prefix, error) {
- return getRoutesFromTable()
+ return GetRoutesFromTable()
}
diff --git a/client/internal/routemanager/systemops/systemops_windows.go b/client/internal/routemanager/systemops/systemops_windows.go
index 88bdce7c96c..0d3630cb8d0 100644
--- a/client/internal/routemanager/systemops/systemops_windows.go
+++ b/client/internal/routemanager/systemops/systemops_windows.go
@@ -94,7 +94,7 @@ func (r *SysOps) removeFromRouteTable(prefix netip.Prefix, nexthop Nexthop) erro
return nil
}
-func getRoutesFromTable() ([]netip.Prefix, error) {
+func GetRoutesFromTable() ([]netip.Prefix, error) {
mux.Lock()
defer mux.Unlock()
diff --git a/client/proto/daemon.pb.go b/client/proto/daemon.pb.go
index 8135402463a..fb10a38d30f 100644
--- a/client/proto/daemon.pb.go
+++ b/client/proto/daemon.pb.go
@@ -1828,8 +1828,9 @@ type DebugBundleRequest struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- Anonymize bool `protobuf:"varint,1,opt,name=anonymize,proto3" json:"anonymize,omitempty"`
- Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"`
+ Anonymize bool `protobuf:"varint,1,opt,name=anonymize,proto3" json:"anonymize,omitempty"`
+ Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"`
+ SystemInfo bool `protobuf:"varint,3,opt,name=systemInfo,proto3" json:"systemInfo,omitempty"`
}
func (x *DebugBundleRequest) Reset() {
@@ -1878,6 +1879,13 @@ func (x *DebugBundleRequest) GetStatus() string {
return ""
}
+func (x *DebugBundleRequest) GetSystemInfo() bool {
+ if x != nil {
+ return x.SystemInfo
+ }
+ return false
+}
+
type DebugBundleResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -2370,11 +2378,13 @@ var file_daemon_proto_rawDesc = []byte{
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
0x6e, 0x2e, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
- 0x02, 0x38, 0x01, 0x22, 0x4a, 0x0a, 0x12, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64,
+ 0x02, 0x38, 0x01, 0x22, 0x6a, 0x0a, 0x12, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64,
0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x6e, 0x6f,
0x6e, 0x79, 0x6d, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x6e,
0x6f, 0x6e, 0x79, 0x6d, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
- 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22,
+ 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
+ 0x1e, 0x0a, 0x0a, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20,
+ 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x22,
0x29, 0x0a, 0x13, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x14, 0x0a, 0x12, 0x47, 0x65,
diff --git a/client/proto/daemon.proto b/client/proto/daemon.proto
index 267eec279be..43c379fb51e 100644
--- a/client/proto/daemon.proto
+++ b/client/proto/daemon.proto
@@ -263,6 +263,7 @@ message Route {
message DebugBundleRequest {
bool anonymize = 1;
string status = 2;
+ bool systemInfo = 3;
}
message DebugBundleResponse {
diff --git a/client/server/debug.go b/client/server/debug.go
index 9b6a5265991..1187f31871f 100644
--- a/client/server/debug.go
+++ b/client/server/debug.go
@@ -1,3 +1,5 @@
+//go:build !android && !ios
+
package server
import (
@@ -6,16 +8,70 @@ import (
"context"
"fmt"
"io"
+ "net"
+ "net/netip"
"os"
+ "sort"
"strings"
+ "time"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/anonymize"
"github.com/netbirdio/netbird/client/internal/peer"
+ "github.com/netbirdio/netbird/client/internal/routemanager/systemops"
"github.com/netbirdio/netbird/client/proto"
)
+const readmeContent = `Netbird debug bundle
+This debug bundle contains the following files:
+
+status.txt: Anonymized status information of the NetBird client.
+client.log: Most recent, anonymized log file of the NetBird client.
+routes.txt: Anonymized system routes, if --system-info flag was provided.
+interfaces.txt: Anonymized network interface information, if --system-info flag was provided.
+config.txt: Anonymized configuration information of the NetBird client.
+
+
+Anonymization Process
+The files in this bundle have been anonymized to protect sensitive information. Here's how the anonymization was applied:
+
+IP Addresses
+
+IPv4 addresses are replaced with addresses starting from 192.51.100.0
+IPv6 addresses are replaced with addresses starting from 100::
+
+IP addresses from non public ranges and well known addresses are not anonymized (e.g. 8.8.8.8, 100.64.0.0/10, addresses starting with 192.168., 172.16., 10., etc.).
+Reoccuring IP addresses are replaced with the same anonymized address.
+
+Note: The anonymized IP addresses in the status file do not match those in the log and routes files. However, the anonymized IP addresses are consistent within the status file and across the routes and log files.
+
+Domains
+All domain names (except for the netbird domains) are replaced with randomly generated strings ending in ".domain". Anonymized domains are consistent across all files in the bundle.
+Reoccuring domain names are replaced with the same anonymized domain.
+
+Routes
+For anonymized routes, the IP addresses are replaced as described above. The prefix length remains unchanged. Note that for prefixes, the anonymized IP might not be a network address, but the prefix length is still correct.
+Network Interfaces
+The interfaces.txt file contains information about network interfaces, including:
+- Interface name
+- Interface index
+- MTU (Maximum Transmission Unit)
+- Flags
+- IP addresses associated with each interface
+
+The IP addresses in the interfaces file are anonymized using the same process as described above. Interface names, indexes, MTUs, and flags are not anonymized.
+
+Configuration
+The config.txt file contains anonymized configuration information of the NetBird client. Sensitive information such as private keys and SSH keys are excluded. The following fields are anonymized:
+- ManagementURL
+- AdminURL
+- NATExternalIPs
+- CustomDNSAddress
+
+Other non-sensitive configuration options are included without anonymization.
+`
+
// DebugBundle creates a debug bundle and returns the location.
func (s *Server) DebugBundle(_ context.Context, req *proto.DebugBundleRequest) (resp *proto.DebugBundleResponse, err error) {
s.mutex.Lock()
@@ -30,93 +86,211 @@ func (s *Server) DebugBundle(_ context.Context, req *proto.DebugBundleRequest) (
return nil, fmt.Errorf("create zip file: %w", err)
}
defer func() {
- if err := bundlePath.Close(); err != nil {
- log.Errorf("failed to close zip file: %v", err)
+ if closeErr := bundlePath.Close(); closeErr != nil && err == nil {
+ err = fmt.Errorf("close zip file: %w", closeErr)
}
if err != nil {
- if err2 := os.Remove(bundlePath.Name()); err2 != nil {
- log.Errorf("Failed to remove zip file: %v", err2)
+ if removeErr := os.Remove(bundlePath.Name()); removeErr != nil {
+ log.Errorf("Failed to remove zip file: %v", removeErr)
}
}
}()
+ if err := s.createArchive(bundlePath, req); err != nil {
+ return nil, err
+ }
+
+ return &proto.DebugBundleResponse{Path: bundlePath.Name()}, nil
+}
+
+func (s *Server) createArchive(bundlePath *os.File, req *proto.DebugBundleRequest) error {
archive := zip.NewWriter(bundlePath)
- defer func() {
- if err := archive.Close(); err != nil {
- log.Errorf("failed to close archive writer: %v", err)
+ if err := s.addReadme(req, archive); err != nil {
+ return fmt.Errorf("add readme: %w", err)
+ }
+
+ if err := s.addStatus(req, archive); err != nil {
+ return fmt.Errorf("add status: %w", err)
+ }
+
+ anonymizer := anonymize.NewAnonymizer(anonymize.DefaultAddresses())
+ status := s.statusRecorder.GetFullStatus()
+ seedFromStatus(anonymizer, &status)
+
+ if err := s.addConfig(req, anonymizer, archive); err != nil {
+ return fmt.Errorf("add config: %w", err)
+ }
+
+ if req.GetSystemInfo() {
+ if err := s.addRoutes(req, anonymizer, archive); err != nil {
+ return fmt.Errorf("add routes: %w", err)
}
- }()
- if status := req.GetStatus(); status != "" {
- filename := "status.txt"
- if req.GetAnonymize() {
- filename = "status.anon.txt"
+ if err := s.addInterfaces(req, anonymizer, archive); err != nil {
+ return fmt.Errorf("add interfaces: %w", err)
}
+ }
+
+ if err := s.addLogfile(req, anonymizer, archive); err != nil {
+ return fmt.Errorf("add log file: %w", err)
+ }
+
+ if err := archive.Close(); err != nil {
+ return fmt.Errorf("close archive writer: %w", err)
+ }
+ return nil
+}
+
+func (s *Server) addReadme(req *proto.DebugBundleRequest, archive *zip.Writer) error {
+ if req.GetAnonymize() {
+ readmeReader := strings.NewReader(readmeContent)
+ if err := addFileToZip(archive, readmeReader, "README.txt"); err != nil {
+ return fmt.Errorf("add README file to zip: %w", err)
+ }
+ }
+ return nil
+}
+
+func (s *Server) addStatus(req *proto.DebugBundleRequest, archive *zip.Writer) error {
+ if status := req.GetStatus(); status != "" {
statusReader := strings.NewReader(status)
- if err := addFileToZip(archive, statusReader, filename); err != nil {
- return nil, fmt.Errorf("add status file to zip: %w", err)
+ if err := addFileToZip(archive, statusReader, "status.txt"); err != nil {
+ return fmt.Errorf("add status file to zip: %w", err)
}
}
+ return nil
+}
+
+func (s *Server) addConfig(req *proto.DebugBundleRequest, anonymizer *anonymize.Anonymizer, archive *zip.Writer) error {
+ var configContent strings.Builder
+ s.addCommonConfigFields(&configContent)
+ if req.GetAnonymize() {
+ if s.config.ManagementURL != nil {
+ configContent.WriteString(fmt.Sprintf("ManagementURL: %s\n", anonymizer.AnonymizeURI(s.config.ManagementURL.String())))
+ }
+ if s.config.AdminURL != nil {
+ configContent.WriteString(fmt.Sprintf("AdminURL: %s\n", anonymizer.AnonymizeURI(s.config.AdminURL.String())))
+ }
+ configContent.WriteString(fmt.Sprintf("NATExternalIPs: %v\n", anonymizeNATExternalIPs(s.config.NATExternalIPs, anonymizer)))
+ if s.config.CustomDNSAddress != "" {
+ configContent.WriteString(fmt.Sprintf("CustomDNSAddress: %s\n", anonymizer.AnonymizeString(s.config.CustomDNSAddress)))
+ }
+ } else {
+ if s.config.ManagementURL != nil {
+ configContent.WriteString(fmt.Sprintf("ManagementURL: %s\n", s.config.ManagementURL.String()))
+ }
+ if s.config.AdminURL != nil {
+ configContent.WriteString(fmt.Sprintf("AdminURL: %s\n", s.config.AdminURL.String()))
+ }
+ configContent.WriteString(fmt.Sprintf("NATExternalIPs: %v\n", s.config.NATExternalIPs))
+ if s.config.CustomDNSAddress != "" {
+ configContent.WriteString(fmt.Sprintf("CustomDNSAddress: %s\n", s.config.CustomDNSAddress))
+ }
+ }
+
+ // Add config content to zip file
+ configReader := strings.NewReader(configContent.String())
+ if err := addFileToZip(archive, configReader, "config.txt"); err != nil {
+ return fmt.Errorf("add config file to zip: %w", err)
+ }
+
+ return nil
+}
+
+func (s *Server) addCommonConfigFields(configContent *strings.Builder) {
+ configContent.WriteString("NetBird Client Configuration:\n\n")
+
+ // Add non-sensitive fields
+ configContent.WriteString(fmt.Sprintf("WgIface: %s\n", s.config.WgIface))
+ configContent.WriteString(fmt.Sprintf("WgPort: %d\n", s.config.WgPort))
+ if s.config.NetworkMonitor != nil {
+ configContent.WriteString(fmt.Sprintf("NetworkMonitor: %v\n", *s.config.NetworkMonitor))
+ }
+ configContent.WriteString(fmt.Sprintf("IFaceBlackList: %v\n", s.config.IFaceBlackList))
+ configContent.WriteString(fmt.Sprintf("DisableIPv6Discovery: %v\n", s.config.DisableIPv6Discovery))
+ configContent.WriteString(fmt.Sprintf("RosenpassEnabled: %v\n", s.config.RosenpassEnabled))
+ configContent.WriteString(fmt.Sprintf("RosenpassPermissive: %v\n", s.config.RosenpassPermissive))
+ if s.config.ServerSSHAllowed != nil {
+ configContent.WriteString(fmt.Sprintf("ServerSSHAllowed: %v\n", *s.config.ServerSSHAllowed))
+ }
+ configContent.WriteString(fmt.Sprintf("DisableAutoConnect: %v\n", s.config.DisableAutoConnect))
+ configContent.WriteString(fmt.Sprintf("DNSRouteInterval: %s\n", s.config.DNSRouteInterval))
+}
+
+func (s *Server) addRoutes(req *proto.DebugBundleRequest, anonymizer *anonymize.Anonymizer, archive *zip.Writer) error {
+ if routes, err := systemops.GetRoutesFromTable(); err != nil {
+ log.Errorf("Failed to get routes: %v", err)
+ } else {
+ // TODO: get routes including nexthop
+ routesContent := formatRoutes(routes, req.GetAnonymize(), anonymizer)
+ routesReader := strings.NewReader(routesContent)
+ if err := addFileToZip(archive, routesReader, "routes.txt"); err != nil {
+ return fmt.Errorf("add routes file to zip: %w", err)
+ }
+ }
+ return nil
+}
+
+func (s *Server) addInterfaces(req *proto.DebugBundleRequest, anonymizer *anonymize.Anonymizer, archive *zip.Writer) error {
+ interfaces, err := net.Interfaces()
+ if err != nil {
+ return fmt.Errorf("get interfaces: %w", err)
+ }
+
+ interfacesContent := formatInterfaces(interfaces, req.GetAnonymize(), anonymizer)
+ interfacesReader := strings.NewReader(interfacesContent)
+ if err := addFileToZip(archive, interfacesReader, "interfaces.txt"); err != nil {
+ return fmt.Errorf("add interfaces file to zip: %w", err)
+ }
+
+ return nil
+}
+
+func (s *Server) addLogfile(req *proto.DebugBundleRequest, anonymizer *anonymize.Anonymizer, archive *zip.Writer) (err error) {
logFile, err := os.Open(s.logFile)
if err != nil {
- return nil, fmt.Errorf("open log file: %w", err)
+ return fmt.Errorf("open log file: %w", err)
}
defer func() {
if err := logFile.Close(); err != nil {
- log.Errorf("failed to close original log file: %v", err)
+ log.Errorf("Failed to close original log file: %v", err)
}
}()
- filename := "client.log.txt"
var logReader io.Reader
- errChan := make(chan error, 1)
if req.GetAnonymize() {
- filename = "client.anon.log.txt"
- var writer io.WriteCloser
+ var writer *io.PipeWriter
logReader, writer = io.Pipe()
- go s.anonymize(logFile, writer, errChan)
+ go s.anonymize(logFile, writer, anonymizer)
} else {
logReader = logFile
}
- if err := addFileToZip(archive, logReader, filename); err != nil {
- return nil, fmt.Errorf("add log file to zip: %w", err)
+ if err := addFileToZip(archive, logReader, "client.log"); err != nil {
+ return fmt.Errorf("add log file to zip: %w", err)
}
- select {
- case err := <-errChan:
- if err != nil {
- return nil, err
- }
- default:
- }
-
- return &proto.DebugBundleResponse{Path: bundlePath.Name()}, nil
+ return nil
}
-func (s *Server) anonymize(reader io.Reader, writer io.WriteCloser, errChan chan<- error) {
- scanner := bufio.NewScanner(reader)
- anonymizer := anonymize.NewAnonymizer(anonymize.DefaultAddresses())
-
- status := s.statusRecorder.GetFullStatus()
- seedFromStatus(anonymizer, &status)
-
+func (s *Server) anonymize(reader io.Reader, writer *io.PipeWriter, anonymizer *anonymize.Anonymizer) {
defer func() {
- if err := writer.Close(); err != nil {
- log.Errorf("Failed to close writer: %v", err)
- }
+ // always nil
+ _ = writer.Close()
}()
+
+ scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := anonymizer.AnonymizeString(scanner.Text())
if _, err := writer.Write([]byte(line + "\n")); err != nil {
- errChan <- fmt.Errorf("write line to writer: %w", err)
+ writer.CloseWithError(fmt.Errorf("anonymize write: %w", err))
return
}
}
if err := scanner.Err(); err != nil {
- errChan <- fmt.Errorf("read line from scanner: %w", err)
+ writer.CloseWithError(fmt.Errorf("anonymize scan: %w", err))
return
}
}
@@ -141,8 +315,22 @@ func (s *Server) SetLogLevel(_ context.Context, req *proto.SetLogLevelRequest) (
func addFileToZip(archive *zip.Writer, reader io.Reader, filename string) error {
header := &zip.FileHeader{
- Name: filename,
- Method: zip.Deflate,
+ Name: filename,
+ Method: zip.Deflate,
+ Modified: time.Now(),
+
+ CreatorVersion: 20, // Version 2.0
+ ReaderVersion: 20, // Version 2.0
+ Flags: 0x800, // UTF-8 filename
+ }
+
+ // If the reader is a file, we can get more accurate information
+ if f, ok := reader.(*os.File); ok {
+ if stat, err := f.Stat(); err != nil {
+ log.Tracef("Failed to get file stat for %s: %v", filename, err)
+ } else {
+ header.Modified = stat.ModTime()
+ }
}
writer, err := archive.CreateHeader(header)
@@ -165,6 +353,13 @@ func seedFromStatus(a *anonymize.Anonymizer, status *peer.FullStatus) {
for _, peer := range status.Peers {
a.AnonymizeDomain(peer.FQDN)
+ for route := range peer.GetRoutes() {
+ a.AnonymizeRoute(route)
+ }
+ }
+
+ for route := range status.LocalPeerState.Routes {
+ a.AnonymizeRoute(route)
}
for _, nsGroup := range status.NSGroupStates {
@@ -179,3 +374,113 @@ func seedFromStatus(a *anonymize.Anonymizer, status *peer.FullStatus) {
}
}
}
+
+func formatRoutes(routes []netip.Prefix, anonymize bool, anonymizer *anonymize.Anonymizer) string {
+ var ipv4Routes, ipv6Routes []netip.Prefix
+
+ // Separate IPv4 and IPv6 routes
+ for _, route := range routes {
+ if route.Addr().Is4() {
+ ipv4Routes = append(ipv4Routes, route)
+ } else {
+ ipv6Routes = append(ipv6Routes, route)
+ }
+ }
+
+ // Sort IPv4 and IPv6 routes separately
+ sort.Slice(ipv4Routes, func(i, j int) bool {
+ return ipv4Routes[i].Bits() > ipv4Routes[j].Bits()
+ })
+ sort.Slice(ipv6Routes, func(i, j int) bool {
+ return ipv6Routes[i].Bits() > ipv6Routes[j].Bits()
+ })
+
+ var builder strings.Builder
+
+ // Format IPv4 routes
+ builder.WriteString("IPv4 Routes:\n")
+ for _, route := range ipv4Routes {
+ formatRoute(&builder, route, anonymize, anonymizer)
+ }
+
+ // Format IPv6 routes
+ builder.WriteString("\nIPv6 Routes:\n")
+ for _, route := range ipv6Routes {
+ formatRoute(&builder, route, anonymize, anonymizer)
+ }
+
+ return builder.String()
+}
+
+func formatRoute(builder *strings.Builder, route netip.Prefix, anonymize bool, anonymizer *anonymize.Anonymizer) {
+ if anonymize {
+ anonymizedIP := anonymizer.AnonymizeIP(route.Addr())
+ builder.WriteString(fmt.Sprintf("%s/%d\n", anonymizedIP, route.Bits()))
+ } else {
+ builder.WriteString(fmt.Sprintf("%s\n", route))
+ }
+}
+
+func formatInterfaces(interfaces []net.Interface, anonymize bool, anonymizer *anonymize.Anonymizer) string {
+ sort.Slice(interfaces, func(i, j int) bool {
+ return interfaces[i].Name < interfaces[j].Name
+ })
+
+ var builder strings.Builder
+ builder.WriteString("Network Interfaces:\n")
+
+ for _, iface := range interfaces {
+ builder.WriteString(fmt.Sprintf("\nInterface: %s\n", iface.Name))
+ builder.WriteString(fmt.Sprintf(" Index: %d\n", iface.Index))
+ builder.WriteString(fmt.Sprintf(" MTU: %d\n", iface.MTU))
+ builder.WriteString(fmt.Sprintf(" Flags: %v\n", iface.Flags))
+
+ addrs, err := iface.Addrs()
+ if err != nil {
+ builder.WriteString(fmt.Sprintf(" Addresses: Error retrieving addresses: %v\n", err))
+ } else {
+ builder.WriteString(" Addresses:\n")
+ for _, addr := range addrs {
+ prefix, err := netip.ParsePrefix(addr.String())
+ if err != nil {
+ builder.WriteString(fmt.Sprintf(" Error parsing address: %v\n", err))
+ continue
+ }
+ ip := prefix.Addr()
+ if anonymize {
+ ip = anonymizer.AnonymizeIP(ip)
+ }
+ builder.WriteString(fmt.Sprintf(" %s/%d\n", ip, prefix.Bits()))
+ }
+ }
+ }
+
+ return builder.String()
+}
+
+func anonymizeNATExternalIPs(ips []string, anonymizer *anonymize.Anonymizer) []string {
+ anonymizedIPs := make([]string, len(ips))
+ for i, ip := range ips {
+ parts := strings.SplitN(ip, "/", 2)
+
+ ip1, err := netip.ParseAddr(parts[0])
+ if err != nil {
+ anonymizedIPs[i] = ip
+ continue
+ }
+ ip1anon := anonymizer.AnonymizeIP(ip1)
+
+ if len(parts) == 2 {
+ ip2, err := netip.ParseAddr(parts[1])
+ if err != nil {
+ anonymizedIPs[i] = fmt.Sprintf("%s/%s", ip1anon, parts[1])
+ } else {
+ ip2anon := anonymizer.AnonymizeIP(ip2)
+ anonymizedIPs[i] = fmt.Sprintf("%s/%s", ip1anon, ip2anon)
+ }
+ } else {
+ anonymizedIPs[i] = ip1anon.String()
+ }
+ }
+ return anonymizedIPs
+}
From bfc33a3f6f49d1d6b43c276d243a48f33c77bc09 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Fri, 2 Aug 2024 14:54:37 +0200
Subject: [PATCH 53/77] Move Bundle to before netbird down (#2377)
This allows to get interface and route information added by the agent
---
client/cmd/debug.go | 49 +++++++++++++++++++++++----------------------
1 file changed, 25 insertions(+), 24 deletions(-)
diff --git a/client/cmd/debug.go b/client/cmd/debug.go
index dbdce91ab29..9abd2039dd5 100644
--- a/client/cmd/debug.go
+++ b/client/cmd/debug.go
@@ -138,17 +138,20 @@ func runForDuration(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to get status: %v", status.Convert(err).Message())
}
- restoreUp := stat.Status == string(internal.StatusConnected) || stat.Status == string(internal.StatusConnecting)
+ stateWasDown := stat.Status != string(internal.StatusConnected) && stat.Status != string(internal.StatusConnecting)
initialLogLevel, err := client.GetLogLevel(cmd.Context(), &proto.GetLogLevelRequest{})
if err != nil {
return fmt.Errorf("failed to get log level: %v", status.Convert(err).Message())
}
- if _, err := client.Down(cmd.Context(), &proto.DownRequest{}); err != nil {
- return fmt.Errorf("failed to down: %v", status.Convert(err).Message())
+ if stateWasDown {
+ if _, err := client.Up(cmd.Context(), &proto.UpRequest{}); err != nil {
+ return fmt.Errorf("failed to up: %v", status.Convert(err).Message())
+ }
+ cmd.Println("Netbird up")
+ time.Sleep(time.Second * 10)
}
- cmd.Println("Netbird down")
initialLevelTrace := initialLogLevel.GetLevel() >= proto.LogLevel_TRACE
if !initialLevelTrace {
@@ -161,6 +164,11 @@ func runForDuration(cmd *cobra.Command, args []string) error {
cmd.Println("Log level set to trace.")
}
+ if _, err := client.Down(cmd.Context(), &proto.DownRequest{}); err != nil {
+ return fmt.Errorf("failed to down: %v", status.Convert(err).Message())
+ }
+ cmd.Println("Netbird down")
+
time.Sleep(1 * time.Second)
if _, err := client.Up(cmd.Context(), &proto.UpRequest{}); err != nil {
@@ -178,21 +186,25 @@ func runForDuration(cmd *cobra.Command, args []string) error {
}
cmd.Println("\nDuration completed")
+ cmd.Println("Creating debug bundle...")
+
headerPreDown := fmt.Sprintf("----- Netbird pre-down - Timestamp: %s - Duration: %s", time.Now().Format(time.RFC3339), duration)
statusOutput = fmt.Sprintf("%s\n%s\n%s", statusOutput, headerPreDown, getStatusOutput(cmd))
- if _, err := client.Down(cmd.Context(), &proto.DownRequest{}); err != nil {
- return fmt.Errorf("failed to down: %v", status.Convert(err).Message())
+ resp, err := client.DebugBundle(cmd.Context(), &proto.DebugBundleRequest{
+ Anonymize: anonymizeFlag,
+ Status: statusOutput,
+ SystemInfo: debugSystemInfoFlag,
+ })
+ if err != nil {
+ return fmt.Errorf("failed to bundle debug: %v", status.Convert(err).Message())
}
- cmd.Println("Netbird down")
-
- time.Sleep(1 * time.Second)
- if restoreUp {
- if _, err := client.Up(cmd.Context(), &proto.UpRequest{}); err != nil {
- return fmt.Errorf("failed to up: %v", status.Convert(err).Message())
+ if stateWasDown {
+ if _, err := client.Down(cmd.Context(), &proto.DownRequest{}); err != nil {
+ return fmt.Errorf("failed to down: %v", status.Convert(err).Message())
}
- cmd.Println("Netbird up")
+ cmd.Println("Netbird down")
}
if !initialLevelTrace {
@@ -202,17 +214,6 @@ func runForDuration(cmd *cobra.Command, args []string) error {
cmd.Println("Log level restored to", initialLogLevel.GetLevel())
}
- cmd.Println("Creating debug bundle...")
-
- resp, err := client.DebugBundle(cmd.Context(), &proto.DebugBundleRequest{
- Anonymize: anonymizeFlag,
- Status: statusOutput,
- SystemInfo: debugSystemInfoFlag,
- })
- if err != nil {
- return fmt.Errorf("failed to bundle debug: %v", status.Convert(err).Message())
- }
-
cmd.Println(resp.GetPath())
return nil
From e6f7222034bb715d4983d02d2009192ca36d973e Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Fri, 2 Aug 2024 18:07:57 +0200
Subject: [PATCH 54/77] Fix Windows file version (#2380)
Systems that validates the binary version didn't like the build number as we set
This fixes the versioning and will use a static build number
---
.github/workflows/release.yml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1889b58e740..89eb2ded90c 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -80,13 +80,13 @@ jobs:
- name: Install goversioninfo
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
- name: Generate windows syso 386
- run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-build ${{ github.run_id }} -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -product-version ${{ steps.semver_parser.outputs.fullversion }}.${{ github.run_id }} -o client/resources_windows_386.syso
+ run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_386.syso
- name: Generate windows syso arm
- run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-build ${{ github.run_id }} -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -product-version ${{ steps.semver_parser.outputs.fullversion }}.${{ github.run_id }} -o client/resources_windows_arm.syso
+ run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_arm.syso
- name: Generate windows syso arm64
- run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-build ${{ github.run_id }} -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -product-version ${{ steps.semver_parser.outputs.fullversion }}.${{ github.run_id }} -o client/resources_windows_arm64.syso
+ run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_arm64.syso
- name: Generate windows syso amd64
- run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-build ${{ github.run_id }} -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -product-version ${{ steps.semver_parser.outputs.fullversion }}.${{ github.run_id }} -o client/resources_windows_amd64.syso
+ run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_amd64.syso
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
@@ -170,7 +170,7 @@ jobs:
- name: Install goversioninfo
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
- name: Generate windows syso amd64
- run: goversioninfo -64 -icon client/ui/netbird.ico -manifest client/ui/manifest.xml -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-build ${{ github.run_id }} -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -product-version ${{ steps.semver_parser.outputs.fullversion }}.${{ github.run_id }} -o client/ui/resources_windows_amd64.syso
+ run: goversioninfo -64 -icon client/ui/netbird.ico -manifest client/ui/manifest.xml -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_amd64.syso
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
From 727a4f075311279e2f9004f51a6759bfa4f94fd7 Mon Sep 17 00:00:00 2001
From: Misha Bragin
Date: Fri, 2 Aug 2024 18:20:13 +0200
Subject: [PATCH 55/77] Remove Codacy badge as it is broken (#2379)
---
README.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/README.md b/README.md
index 5be1826b475..98dd18d3744 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,6 @@
-
From 501fd93e479cc58d4a447e84437aa52023145f80 Mon Sep 17 00:00:00 2001
From: pascal-fischer <32096965+pascal-fischer@users.noreply.github.com>
Date: Fri, 2 Aug 2024 18:43:00 +0200
Subject: [PATCH 56/77] Fix DNS resolution for routes on iOS (#2378)
---
client/internal/dns/server.go | 6 +-
client/internal/dns/server_test.go | 2 +-
client/internal/dns/service_memory.go | 20 +++----
client/internal/dns/upstream_ios.go | 38 +++++++------
client/internal/routemanager/client.go | 8 ++-
client/internal/routemanager/dynamic/route.go | 19 ++++++-
.../routemanager/dynamic/route_generic.go | 13 +++++
.../routemanager/dynamic/route_ios.go | 55 +++++++++++++++++++
8 files changed, 124 insertions(+), 37 deletions(-)
create mode 100644 client/internal/routemanager/dynamic/route_generic.go
create mode 100644 client/internal/routemanager/dynamic/route_ios.go
diff --git a/client/internal/dns/server.go b/client/internal/dns/server.go
index 267c1ed8071..a4651ebb5b0 100644
--- a/client/internal/dns/server.go
+++ b/client/internal/dns/server.go
@@ -94,7 +94,7 @@ func NewDefaultServer(
var dnsService service
if wgInterface.IsUserspaceBind() {
- dnsService = newServiceViaMemory(wgInterface)
+ dnsService = NewServiceViaMemory(wgInterface)
} else {
dnsService = newServiceViaListener(wgInterface, addrPort)
}
@@ -112,7 +112,7 @@ func NewDefaultServerPermanentUpstream(
statusRecorder *peer.Status,
) *DefaultServer {
log.Debugf("host dns address list is: %v", hostsDnsList)
- ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface), statusRecorder)
+ ds := newDefaultServer(ctx, wgInterface, NewServiceViaMemory(wgInterface), statusRecorder)
ds.hostsDNSHolder.set(hostsDnsList)
ds.permanent = true
ds.addHostRootZone()
@@ -130,7 +130,7 @@ func NewDefaultServerIos(
iosDnsManager IosDnsManager,
statusRecorder *peer.Status,
) *DefaultServer {
- ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface), statusRecorder)
+ ds := newDefaultServer(ctx, wgInterface, NewServiceViaMemory(wgInterface), statusRecorder)
ds.iosDnsManager = iosDnsManager
return ds
}
diff --git a/client/internal/dns/server_test.go b/client/internal/dns/server_test.go
index 6cbd9ea1527..b9552bc17c0 100644
--- a/client/internal/dns/server_test.go
+++ b/client/internal/dns/server_test.go
@@ -534,7 +534,7 @@ func TestDNSServerStartStop(t *testing.T) {
func TestDNSServerUpstreamDeactivateCallback(t *testing.T) {
hostManager := &mockHostConfigurator{}
server := DefaultServer{
- service: newServiceViaMemory(&mocWGIface{}),
+ service: NewServiceViaMemory(&mocWGIface{}),
localResolver: &localResolver{
registeredMap: make(registrationMap),
},
diff --git a/client/internal/dns/service_memory.go b/client/internal/dns/service_memory.go
index 757cd962aab..729b90cc027 100644
--- a/client/internal/dns/service_memory.go
+++ b/client/internal/dns/service_memory.go
@@ -12,7 +12,7 @@ import (
log "github.com/sirupsen/logrus"
)
-type serviceViaMemory struct {
+type ServiceViaMemory struct {
wgInterface WGIface
dnsMux *dns.ServeMux
runtimeIP string
@@ -22,8 +22,8 @@ type serviceViaMemory struct {
listenerFlagLock sync.Mutex
}
-func newServiceViaMemory(wgIface WGIface) *serviceViaMemory {
- s := &serviceViaMemory{
+func NewServiceViaMemory(wgIface WGIface) *ServiceViaMemory {
+ s := &ServiceViaMemory{
wgInterface: wgIface,
dnsMux: dns.NewServeMux(),
@@ -33,7 +33,7 @@ func newServiceViaMemory(wgIface WGIface) *serviceViaMemory {
return s
}
-func (s *serviceViaMemory) Listen() error {
+func (s *ServiceViaMemory) Listen() error {
s.listenerFlagLock.Lock()
defer s.listenerFlagLock.Unlock()
@@ -52,7 +52,7 @@ func (s *serviceViaMemory) Listen() error {
return nil
}
-func (s *serviceViaMemory) Stop() {
+func (s *ServiceViaMemory) Stop() {
s.listenerFlagLock.Lock()
defer s.listenerFlagLock.Unlock()
@@ -67,23 +67,23 @@ func (s *serviceViaMemory) Stop() {
s.listenerIsRunning = false
}
-func (s *serviceViaMemory) RegisterMux(pattern string, handler dns.Handler) {
+func (s *ServiceViaMemory) RegisterMux(pattern string, handler dns.Handler) {
s.dnsMux.Handle(pattern, handler)
}
-func (s *serviceViaMemory) DeregisterMux(pattern string) {
+func (s *ServiceViaMemory) DeregisterMux(pattern string) {
s.dnsMux.HandleRemove(pattern)
}
-func (s *serviceViaMemory) RuntimePort() int {
+func (s *ServiceViaMemory) RuntimePort() int {
return s.runtimePort
}
-func (s *serviceViaMemory) RuntimeIP() string {
+func (s *ServiceViaMemory) RuntimeIP() string {
return s.runtimeIP
}
-func (s *serviceViaMemory) filterDNSTraffic() (string, error) {
+func (s *ServiceViaMemory) filterDNSTraffic() (string, error) {
filter := s.wgInterface.GetFilter()
if filter == nil {
return "", fmt.Errorf("can't set DNS filter, filter not initialized")
diff --git a/client/internal/dns/upstream_ios.go b/client/internal/dns/upstream_ios.go
index 0c01a013e43..60ed79d8769 100644
--- a/client/internal/dns/upstream_ios.go
+++ b/client/internal/dns/upstream_ios.go
@@ -4,6 +4,7 @@ package dns
import (
"context"
+ "fmt"
"net"
"syscall"
"time"
@@ -17,9 +18,9 @@ import (
type upstreamResolverIOS struct {
*upstreamResolverBase
- lIP net.IP
- lNet *net.IPNet
- iIndex int
+ lIP net.IP
+ lNet *net.IPNet
+ interfaceName string
}
func newUpstreamResolver(
@@ -32,17 +33,11 @@ func newUpstreamResolver(
) (*upstreamResolverIOS, error) {
upstreamResolverBase := newUpstreamResolverBase(ctx, statusRecorder)
- index, err := getInterfaceIndex(interfaceName)
- if err != nil {
- log.Debugf("unable to get interface index for %s: %s", interfaceName, err)
- return nil, err
- }
-
ios := &upstreamResolverIOS{
upstreamResolverBase: upstreamResolverBase,
lIP: ip,
lNet: net,
- iIndex: index,
+ interfaceName: interfaceName,
}
ios.upstreamClient = ios
@@ -53,7 +48,7 @@ func (u *upstreamResolverIOS) exchange(ctx context.Context, upstream string, r *
client := &dns.Client{}
upstreamHost, _, err := net.SplitHostPort(upstream)
if err != nil {
- log.Errorf("error while parsing upstream host: %s", err)
+ return nil, 0, fmt.Errorf("error while parsing upstream host: %s", err)
}
timeout := upstreamTimeout
@@ -65,26 +60,35 @@ func (u *upstreamResolverIOS) exchange(ctx context.Context, upstream string, r *
upstreamIP := net.ParseIP(upstreamHost)
if u.lNet.Contains(upstreamIP) || net.IP.IsPrivate(upstreamIP) {
log.Debugf("using private client to query upstream: %s", upstream)
- client = u.getClientPrivate(timeout)
+ client, err = GetClientPrivate(u.lIP, u.interfaceName, timeout)
+ if err != nil {
+ return nil, 0, fmt.Errorf("error while creating private client: %s", err)
+ }
}
// Cannot use client.ExchangeContext because it overwrites our Dialer
return client.Exchange(r, upstream)
}
-// getClientPrivate returns a new DNS client bound to the local IP address of the Netbird interface
+// GetClientPrivate returns a new DNS client bound to the local IP address of the Netbird interface
// This method is needed for iOS
-func (u *upstreamResolverIOS) getClientPrivate(dialTimeout time.Duration) *dns.Client {
+func GetClientPrivate(ip net.IP, interfaceName string, dialTimeout time.Duration) (*dns.Client, error) {
+ index, err := getInterfaceIndex(interfaceName)
+ if err != nil {
+ log.Debugf("unable to get interface index for %s: %s", interfaceName, err)
+ return nil, err
+ }
+
dialer := &net.Dialer{
LocalAddr: &net.UDPAddr{
- IP: u.lIP,
+ IP: ip,
Port: 0, // Let the OS pick a free port
},
Timeout: dialTimeout,
Control: func(network, address string, c syscall.RawConn) error {
var operr error
fn := func(s uintptr) {
- operr = unix.SetsockoptInt(int(s), unix.IPPROTO_IP, unix.IP_BOUND_IF, u.iIndex)
+ operr = unix.SetsockoptInt(int(s), unix.IPPROTO_IP, unix.IP_BOUND_IF, index)
}
if err := c.Control(fn); err != nil {
@@ -101,7 +105,7 @@ func (u *upstreamResolverIOS) getClientPrivate(dialTimeout time.Duration) *dns.C
client := &dns.Client{
Dialer: dialer,
}
- return client
+ return client, nil
}
func getInterfaceIndex(interfaceName string) (int, error) {
diff --git a/client/internal/routemanager/client.go b/client/internal/routemanager/client.go
index 92c71b1e035..1566d10dd5d 100644
--- a/client/internal/routemanager/client.go
+++ b/client/internal/routemanager/client.go
@@ -10,6 +10,7 @@ import (
log "github.com/sirupsen/logrus"
nberrors "github.com/netbirdio/netbird/client/errors"
+ nbdns "github.com/netbirdio/netbird/client/internal/dns"
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/internal/routemanager/dynamic"
"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
@@ -65,7 +66,7 @@ func newClientNetworkWatcher(ctx context.Context, dnsRouteInterval time.Duration
routePeersNotifiers: make(map[string]chan struct{}),
routeUpdate: make(chan routesUpdate),
peerStateUpdate: make(chan struct{}),
- handler: handlerFromRoute(rt, routeRefCounter, allowedIPsRefCounter, dnsRouteInterval, statusRecorder),
+ handler: handlerFromRoute(rt, routeRefCounter, allowedIPsRefCounter, dnsRouteInterval, statusRecorder, wgInterface),
}
return client
}
@@ -383,9 +384,10 @@ func (c *clientNetwork) peersStateAndUpdateWatcher() {
}
}
-func handlerFromRoute(rt *route.Route, routeRefCounter *refcounter.RouteRefCounter, allowedIPsRefCounter *refcounter.AllowedIPsRefCounter, dnsRouterInteval time.Duration, statusRecorder *peer.Status) RouteHandler {
+func handlerFromRoute(rt *route.Route, routeRefCounter *refcounter.RouteRefCounter, allowedIPsRefCounter *refcounter.AllowedIPsRefCounter, dnsRouterInteval time.Duration, statusRecorder *peer.Status, wgInterface *iface.WGIface) RouteHandler {
if rt.IsDynamic() {
- return dynamic.NewRoute(rt, routeRefCounter, allowedIPsRefCounter, dnsRouterInteval, statusRecorder)
+ dns := nbdns.NewServiceViaMemory(wgInterface)
+ return dynamic.NewRoute(rt, routeRefCounter, allowedIPsRefCounter, dnsRouterInteval, statusRecorder, wgInterface, fmt.Sprintf("%s:%d", dns.RuntimeIP(), dns.RuntimePort()))
}
return static.NewRoute(rt, routeRefCounter, allowedIPsRefCounter)
}
diff --git a/client/internal/routemanager/dynamic/route.go b/client/internal/routemanager/dynamic/route.go
index e957107980b..3296f3ddf34 100644
--- a/client/internal/routemanager/dynamic/route.go
+++ b/client/internal/routemanager/dynamic/route.go
@@ -16,6 +16,7 @@ import (
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
"github.com/netbirdio/netbird/client/internal/routemanager/util"
+ "github.com/netbirdio/netbird/iface"
"github.com/netbirdio/netbird/management/domain"
"github.com/netbirdio/netbird/route"
)
@@ -47,6 +48,8 @@ type Route struct {
currentPeerKey string
cancel context.CancelFunc
statusRecorder *peer.Status
+ wgInterface *iface.WGIface
+ resolverAddr string
}
func NewRoute(
@@ -55,6 +58,8 @@ func NewRoute(
allowedIPsRefCounter *refcounter.AllowedIPsRefCounter,
interval time.Duration,
statusRecorder *peer.Status,
+ wgInterface *iface.WGIface,
+ resolverAddr string,
) *Route {
return &Route{
route: rt,
@@ -63,6 +68,8 @@ func NewRoute(
interval: interval,
dynamicDomains: domainMap{},
statusRecorder: statusRecorder,
+ wgInterface: wgInterface,
+ resolverAddr: resolverAddr,
}
}
@@ -228,11 +235,17 @@ func (r *Route) resolve(results chan resolveResult) {
wg.Add(1)
go func(domain domain.Domain) {
defer wg.Done()
- ips, err := net.LookupIP(string(domain))
+
+ ips, err := r.getIPsFromResolver(domain)
if err != nil {
- results <- resolveResult{domain: domain, err: fmt.Errorf("resolve d %s: %w", domain.SafeString(), err)}
- return
+ log.Tracef("Failed to resolve domain %s with private resolver: %v", domain.SafeString(), err)
+ ips, err = net.LookupIP(string(domain))
+ if err != nil {
+ results <- resolveResult{domain: domain, err: fmt.Errorf("resolve d %s: %w", domain.SafeString(), err)}
+ return
+ }
}
+
for _, ip := range ips {
prefix, err := util.GetPrefixFromIP(ip)
if err != nil {
diff --git a/client/internal/routemanager/dynamic/route_generic.go b/client/internal/routemanager/dynamic/route_generic.go
new file mode 100644
index 00000000000..cf3d913a41a
--- /dev/null
+++ b/client/internal/routemanager/dynamic/route_generic.go
@@ -0,0 +1,13 @@
+//go:build !ios
+
+package dynamic
+
+import (
+ "net"
+
+ "github.com/netbirdio/netbird/management/domain"
+)
+
+func (r *Route) getIPsFromResolver(domain domain.Domain) ([]net.IP, error) {
+ return net.LookupIP(string(domain))
+}
diff --git a/client/internal/routemanager/dynamic/route_ios.go b/client/internal/routemanager/dynamic/route_ios.go
new file mode 100644
index 00000000000..67138222f44
--- /dev/null
+++ b/client/internal/routemanager/dynamic/route_ios.go
@@ -0,0 +1,55 @@
+//go:build ios
+
+package dynamic
+
+import (
+ "fmt"
+ "net"
+ "time"
+
+ "github.com/miekg/dns"
+
+ nbdns "github.com/netbirdio/netbird/client/internal/dns"
+
+ "github.com/netbirdio/netbird/management/domain"
+)
+
+const dialTimeout = 10 * time.Second
+
+func (r *Route) getIPsFromResolver(domain domain.Domain) ([]net.IP, error) {
+ privateClient, err := nbdns.GetClientPrivate(r.wgInterface.Address().IP, r.wgInterface.Name(), dialTimeout)
+ if err != nil {
+ return nil, fmt.Errorf("error while creating private client: %s", err)
+ }
+
+ msg := new(dns.Msg)
+ msg.SetQuestion(dns.Fqdn(string(domain)), dns.TypeA)
+
+ startTime := time.Now()
+
+ response, _, err := privateClient.Exchange(msg, r.resolverAddr)
+ if err != nil {
+ return nil, fmt.Errorf("DNS query for %s failed after %s: %s ", domain.SafeString(), time.Since(startTime), err)
+ }
+
+ if response.Rcode != dns.RcodeSuccess {
+ return nil, fmt.Errorf("dns response code: %s", dns.RcodeToString[response.Rcode])
+ }
+
+ ips := make([]net.IP, 0)
+
+ for _, answ := range response.Answer {
+ if aRecord, ok := answ.(*dns.A); ok {
+ ips = append(ips, aRecord.A)
+ }
+ if aaaaRecord, ok := answ.(*dns.AAAA); ok {
+ ips = append(ips, aaaaRecord.AAAA)
+ }
+ }
+
+ if len(ips) == 0 {
+ return nil, fmt.Errorf("no A or AAAA records found for %s", domain.SafeString())
+ }
+
+ return ips, nil
+}
From 0371f529cae15c2429c8d18fb92a5cabaf77109f Mon Sep 17 00:00:00 2001
From: pascal-fischer <32096965+pascal-fischer@users.noreply.github.com>
Date: Fri, 2 Aug 2024 18:48:12 +0200
Subject: [PATCH 57/77] Add sonar badge (#2381)
---
README.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/README.md b/README.md
index 98dd18d3744..3704454127a 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,9 @@
+
+
+
From 059fc7c3a2182756ac8830353841b2e3747e22f8 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Sat, 3 Aug 2024 20:15:19 +0200
Subject: [PATCH 58/77] Use docker compose command (#2382)
replace calls to docker-compose with docker compose
---
.github/workflows/test-infrastructure-files.yml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/test-infrastructure-files.yml b/.github/workflows/test-infrastructure-files.yml
index abdd18ceb4a..52b8ee3e2b2 100644
--- a/.github/workflows/test-infrastructure-files.yml
+++ b/.github/workflows/test-infrastructure-files.yml
@@ -151,10 +151,10 @@ jobs:
- name: run docker compose up
working-directory: infrastructure_files/artifacts
run: |
- docker-compose up -d
+ docker compose up -d
sleep 5
- docker-compose ps
- docker-compose logs --tail=20
+ docker compose ps
+ docker compose logs --tail=20
- name: test running containers
run: |
@@ -207,7 +207,7 @@ jobs:
- name: Postgres run cleanup
run: |
- docker-compose down --volumes --rmi all
+ docker compose down --volumes --rmi all
rm -rf docker-compose.yml Caddyfile zitadel.env dashboard.env machinekey/zitadel-admin-sa.token turnserver.conf management.json zdb.env
- name: run script with Zitadel CockroachDB
From 6b930271fd87b1dd27f2074736194928a3b22161 Mon Sep 17 00:00:00 2001
From: Evgenii
Date: Sun, 4 Aug 2024 21:13:08 +0100
Subject: [PATCH 59/77] change default config location on freebsd (#2388)
---
client/cmd/root.go | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/client/cmd/root.go b/client/cmd/root.go
index 010ffb25a60..db02ff5eaad 100644
--- a/client/cmd/root.go
+++ b/client/cmd/root.go
@@ -93,12 +93,15 @@ func init() {
oldDefaultConfigPathDir = "/etc/wiretrustee/"
oldDefaultLogFileDir = "/var/log/wiretrustee/"
- if runtime.GOOS == "windows" {
+ switch runtime.GOOS {
+ case "windows":
defaultConfigPathDir = os.Getenv("PROGRAMDATA") + "\\Netbird\\"
defaultLogFileDir = os.Getenv("PROGRAMDATA") + "\\Netbird\\"
oldDefaultConfigPathDir = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\"
oldDefaultLogFileDir = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\"
+ case "freebsd":
+ defaultConfigPathDir = "/var/db/netbird/"
}
defaultConfigPath = defaultConfigPathDir + "config.json"
From d56dfae9b883c57f150201bfaf913525f785a3fe Mon Sep 17 00:00:00 2001
From: Evgenii
Date: Sun, 4 Aug 2024 21:31:43 +0100
Subject: [PATCH 60/77] Offer only Device Code Flow on FreeBSD (#2389)
---
client/internal/auth/oauth.go | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/client/internal/auth/oauth.go b/client/internal/auth/oauth.go
index 7467584a34b..c9f10ca863a 100644
--- a/client/internal/auth/oauth.go
+++ b/client/internal/auth/oauth.go
@@ -69,6 +69,11 @@ func NewOAuthFlow(ctx context.Context, config *internal.Config, isLinuxDesktopCl
return authenticateWithDeviceCodeFlow(ctx, config)
}
+ // On FreeBSD we currently do not support desktop environments and offer only Device Code Flow (#2384)
+ if runtime.GOOS == "freebsd" {
+ return authenticateWithDeviceCodeFlow(ctx, config)
+ }
+
pkceFlow, err := authenticateWithPKCEFlow(ctx, config)
if err != nil {
// fallback to device code flow
From 1802e51213df139f942527522e5cea828a99217e Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Mon, 5 Aug 2024 11:03:14 +0200
Subject: [PATCH 61/77] Fix windows binary version (#2390)
---
.github/workflows/release.yml | 11 ++---------
1 file changed, 2 insertions(+), 9 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 89eb2ded90c..30f24e92ed8 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -79,15 +79,8 @@ jobs:
- name: Install goversioninfo
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
- - name: Generate windows syso 386
- run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_386.syso
- - name: Generate windows syso arm
- run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_arm.syso
- - name: Generate windows syso arm64
- run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_arm64.syso
- name: Generate windows syso amd64
- run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_amd64.syso
-
+ run: goversioninfo -icon client/ui/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_amd64.syso
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
with:
@@ -170,7 +163,7 @@ jobs:
- name: Install goversioninfo
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
- name: Generate windows syso amd64
- run: goversioninfo -64 -icon client/ui/netbird.ico -manifest client/ui/manifest.xml -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_amd64.syso
+ run: goversioninfo -64 -icon client/ui/netbird.ico -manifest client/ui/manifest.xml -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_amd64.syso
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
From 855fba8fac230b54f585479eb84b4f9500996001 Mon Sep 17 00:00:00 2001
From: pascal-fischer <32096965+pascal-fischer@users.noreply.github.com>
Date: Tue, 6 Aug 2024 22:30:19 +0200
Subject: [PATCH 62/77] On iOS add error handling for getRouteselector (#2394)
---
client/ios/NetBirdSDK/client.go | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/client/ios/NetBirdSDK/client.go b/client/ios/NetBirdSDK/client.go
index d80072c78ad..779c27a4d91 100644
--- a/client/ios/NetBirdSDK/client.go
+++ b/client/ios/NetBirdSDK/client.go
@@ -271,7 +271,14 @@ func (c *Client) GetRoutesSelectionDetails() (*RoutesSelectionDetails, error) {
}
routesMap := engine.GetClientRoutesWithNetID()
- routeSelector := engine.GetRouteManager().GetRouteSelector()
+ routeManager := engine.GetRouteManager()
+ if routeManager == nil {
+ return nil, fmt.Errorf("could not get route manager")
+ }
+ routeSelector := routeManager.GetRouteSelector()
+ if routeSelector == nil {
+ return nil, fmt.Errorf("could not get route selector")
+ }
var routes []*selectRoute
for id, rt := range routesMap {
From 54d896846b15fd462414e3bb7b8e1b9f083741be Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Wed, 7 Aug 2024 10:22:12 +0200
Subject: [PATCH 63/77] Skip network map check if not regular user (#2402)
when getting all peers we don't need to calculate network map when not a regular user
---
management/server/peer.go | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/management/server/peer.go b/management/server/peer.go
index 998a9e53b3b..05fb11236cb 100644
--- a/management/server/peer.go
+++ b/management/server/peer.go
@@ -65,12 +65,14 @@ func (am *DefaultAccountManager) GetPeers(ctx context.Context, accountID, userID
peers := make([]*nbpeer.Peer, 0)
peersMap := make(map[string]*nbpeer.Peer)
- if !user.HasAdminPower() && !user.IsServiceUser && account.Settings.RegularUsersViewBlocked {
+ regularUser := !user.HasAdminPower() && !user.IsServiceUser
+
+ if regularUser && account.Settings.RegularUsersViewBlocked {
return peers, nil
}
for _, peer := range account.Peers {
- if !(user.HasAdminPower() || user.IsServiceUser) && user.Id != peer.UserID {
+ if regularUser && user.Id != peer.UserID {
// only display peers that belong to the current user if the current user is not an admin
continue
}
@@ -79,6 +81,10 @@ func (am *DefaultAccountManager) GetPeers(ctx context.Context, accountID, userID
peersMap[peer.ID] = p
}
+ if !regularUser {
+ return peers, nil
+ }
+
// fetch all the peers that have access to the user's peers
for _, peer := range peers {
aclPeers, _ := account.getPeerConnectionResources(ctx, peer.ID, approvedPeersMap)
From ac0d5ff9f3252bdc31514aa2e16ad59916b13a0e Mon Sep 17 00:00:00 2001
From: Viktor Liu <17948409+lixmal@users.noreply.github.com>
Date: Wed, 7 Aug 2024 10:52:31 +0200
Subject: [PATCH 64/77] [management] Improve mgmt sync performance (#2363)
---
client/cmd/testutil_test.go | 8 +-
client/internal/engine_test.go | 7 +-
client/server/server_test.go | 7 +-
management/client/client_test.go | 9 +-
management/cmd/management.go | 2 +-
management/server/account.go | 88 ++++-
management/server/account_test.go | 21 +-
management/server/dns.go | 142 ++++---
management/server/dns_test.go | 158 +++++++-
management/server/grpcserver.go | 73 ++--
management/server/http/peers_handler.go | 21 +-
management/server/management_proto_test.go | 7 +-
management/server/management_test.go | 10 +-
management/server/nameserver_test.go | 7 +-
management/server/peer.go | 46 ++-
management/server/peer/peer.go | 3 +-
management/server/peer/peer_test.go | 31 ++
management/server/peer_test.go | 362 ++++++++++++++++++
management/server/policy.go | 23 +-
management/server/route_test.go | 7 +-
.../telemetry/accountmanager_metrics.go | 69 ++++
management/server/telemetry/app_metrics.go | 68 ++--
22 files changed, 1000 insertions(+), 169 deletions(-)
create mode 100644 management/server/peer/peer_test.go
create mode 100644 management/server/telemetry/accountmanager_metrics.go
diff --git a/client/cmd/testutil_test.go b/client/cmd/testutil_test.go
index b4c2791d839..984aa6df72f 100644
--- a/client/cmd/testutil_test.go
+++ b/client/cmd/testutil_test.go
@@ -11,6 +11,7 @@ import (
"go.opentelemetry.io/otel"
"github.com/netbirdio/netbird/management/server/activity"
+ "github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/util"
@@ -71,6 +72,7 @@ func startSignal(t *testing.T) (*grpc.Server, net.Listener) {
func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Listener) {
t.Helper()
+
lis, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatal(err)
@@ -88,7 +90,11 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
return nil, nil
}
iv, _ := integrations.NewIntegratedValidator(context.Background(), eventStore)
- accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, iv)
+
+ metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
+ require.NoError(t, err)
+
+ accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, iv, metrics)
if err != nil {
t.Fatal(err)
}
diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go
index 6c6f79d071e..e0f85d21138 100644
--- a/client/internal/engine_test.go
+++ b/client/internal/engine_test.go
@@ -36,6 +36,7 @@ import (
mgmtProto "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/activity"
+ "github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/route"
signal "github.com/netbirdio/netbird/signal/client"
"github.com/netbirdio/netbird/signal/proto"
@@ -1069,7 +1070,11 @@ func startManagement(t *testing.T, dataDir string) (*grpc.Server, string, error)
return nil, "", err
}
ia, _ := integrations.NewIntegratedValidator(context.Background(), eventStore)
- accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia)
+
+ metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
+ require.NoError(t, err)
+
+ accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics)
if err != nil {
return nil, "", err
}
diff --git a/client/server/server_test.go b/client/server/server_test.go
index b19e4615fc7..6a3de774cc9 100644
--- a/client/server/server_test.go
+++ b/client/server/server_test.go
@@ -19,6 +19,7 @@ import (
mgmtProto "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/activity"
+ "github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/signal/proto"
signalServer "github.com/netbirdio/netbird/signal/server"
)
@@ -120,7 +121,11 @@ func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Serve
return nil, "", err
}
ia, _ := integrations.NewIntegratedValidator(context.Background(), eventStore)
- accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia)
+
+ metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
+ require.NoError(t, err)
+
+ accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics)
if err != nil {
return nil, "", err
}
diff --git a/management/client/client_test.go b/management/client/client_test.go
index 2774f2b599d..cec3e77f254 100644
--- a/management/client/client_test.go
+++ b/management/client/client_test.go
@@ -9,7 +9,10 @@ import (
"testing"
"time"
+ "github.com/stretchr/testify/require"
+
"github.com/netbirdio/netbird/management/server/activity"
+ "github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/client/system"
@@ -71,7 +74,11 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
peersUpdateManager := mgmt.NewPeersUpdateManager(nil)
eventStore := &activity.InMemoryEventStore{}
ia, _ := integrations.NewIntegratedValidator(context.Background(), eventStore)
- accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia)
+
+ metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
+ require.NoError(t, err)
+
+ accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics)
if err != nil {
t.Fatal(err)
}
diff --git a/management/cmd/management.go b/management/cmd/management.go
index b87c386c630..a0176c5488d 100644
--- a/management/cmd/management.go
+++ b/management/cmd/management.go
@@ -190,7 +190,7 @@ var (
return fmt.Errorf("failed to initialize integrated peer validator: %v", err)
}
accountManager, err := server.BuildManager(ctx, store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain,
- dnsDomain, eventStore, geo, userDeleteFromIDPEnabled, integratedPeerValidator)
+ dnsDomain, eventStore, geo, userDeleteFromIDPEnabled, integratedPeerValidator, appMetrics)
if err != nil {
return fmt.Errorf("failed to build default manager: %v", err)
}
diff --git a/management/server/account.go b/management/server/account.go
index 5d3ee6dc109..e99e0e7f361 100644
--- a/management/server/account.go
+++ b/management/server/account.go
@@ -18,6 +18,8 @@ import (
"github.com/eko/gocache/v3/cache"
cacheStore "github.com/eko/gocache/v3/store"
+ "github.com/hashicorp/go-multierror"
+ "github.com/miekg/dns"
gocache "github.com/patrickmn/go-cache"
"github.com/rs/xid"
log "github.com/sirupsen/logrus"
@@ -37,6 +39,7 @@ import (
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/posture"
"github.com/netbirdio/netbird/management/server/status"
+ "github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/route"
)
@@ -170,6 +173,8 @@ type DefaultAccountManager struct {
userDeleteFromIDPEnabled bool
integratedPeerValidator integrated_validator.IntegratedValidator
+
+ metrics telemetry.AppMetrics
}
// Settings represents Account settings structure that can be modified via API and Dashboard
@@ -401,8 +406,16 @@ func (a *Account) GetGroup(groupID string) *nbgroup.Group {
return a.Groups[groupID]
}
-// GetPeerNetworkMap returns a group by ID if exists, nil otherwise
-func (a *Account) GetPeerNetworkMap(ctx context.Context, peerID, dnsDomain string, validatedPeersMap map[string]struct{}) *NetworkMap {
+// GetPeerNetworkMap returns the networkmap for the given peer ID.
+func (a *Account) GetPeerNetworkMap(
+ ctx context.Context,
+ peerID string,
+ peersCustomZone nbdns.CustomZone,
+ validatedPeersMap map[string]struct{},
+ metrics *telemetry.AccountManagerMetrics,
+) *NetworkMap {
+ start := time.Now()
+
peer := a.Peers[peerID]
if peer == nil {
return &NetworkMap{
@@ -438,7 +451,7 @@ func (a *Account) GetPeerNetworkMap(ctx context.Context, peerID, dnsDomain strin
if dnsManagementStatus {
var zones []nbdns.CustomZone
- peersCustomZone := getPeersCustomZone(ctx, a, dnsDomain)
+
if peersCustomZone.Domain != "" {
zones = append(zones, peersCustomZone)
}
@@ -446,7 +459,7 @@ func (a *Account) GetPeerNetworkMap(ctx context.Context, peerID, dnsDomain strin
dnsUpdate.NameServerGroups = getPeerNSGroups(a, peerID)
}
- return &NetworkMap{
+ nm := &NetworkMap{
Peers: peersToConnect,
Network: a.Network.Copy(),
Routes: routesUpdate,
@@ -454,6 +467,60 @@ func (a *Account) GetPeerNetworkMap(ctx context.Context, peerID, dnsDomain strin
OfflinePeers: expiredPeers,
FirewallRules: firewallRules,
}
+
+ if metrics != nil {
+ objectCount := int64(len(peersToConnect) + len(expiredPeers) + len(routesUpdate) + len(firewallRules))
+ metrics.CountNetworkMapObjects(objectCount)
+ metrics.CountGetPeerNetworkMapDuration(time.Since(start))
+ }
+
+ return nm
+}
+
+func (a *Account) GetPeersCustomZone(ctx context.Context, dnsDomain string) nbdns.CustomZone {
+ var merr *multierror.Error
+
+ if dnsDomain == "" {
+ log.WithContext(ctx).Error("no dns domain is set, returning empty zone")
+ return nbdns.CustomZone{}
+ }
+
+ customZone := nbdns.CustomZone{
+ Domain: dns.Fqdn(dnsDomain),
+ Records: make([]nbdns.SimpleRecord, 0, len(a.Peers)),
+ }
+
+ domainSuffix := "." + dnsDomain
+
+ var sb strings.Builder
+ for _, peer := range a.Peers {
+ if peer.DNSLabel == "" {
+ merr = multierror.Append(merr, fmt.Errorf("peer %s has an empty DNS label", peer.Name))
+ continue
+ }
+
+ sb.Grow(len(peer.DNSLabel) + len(domainSuffix))
+ sb.WriteString(peer.DNSLabel)
+ sb.WriteString(domainSuffix)
+
+ customZone.Records = append(customZone.Records, nbdns.SimpleRecord{
+ Name: sb.String(),
+ Type: int(dns.TypeA),
+ Class: nbdns.DefaultClass,
+ TTL: defaultTTL,
+ RData: peer.IP.String(),
+ })
+
+ sb.Reset()
+ }
+
+ go func() {
+ if merr != nil {
+ log.WithContext(ctx).Errorf("error generating custom zone for account %s: %v", a.Id, merr)
+ }
+ }()
+
+ return customZone
}
// GetExpiredPeers returns peers that have been expired
@@ -871,10 +938,18 @@ func (a *Account) UserGroupsRemoveFromPeers(userID string, groups ...string) {
}
// BuildManager creates a new DefaultAccountManager with a provided Store
-func BuildManager(ctx context.Context, store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager,
- singleAccountModeDomain string, dnsDomain string, eventStore activity.Store, geo *geolocation.Geolocation,
+func BuildManager(
+ ctx context.Context,
+ store Store,
+ peersUpdateManager *PeersUpdateManager,
+ idpManager idp.Manager,
+ singleAccountModeDomain string,
+ dnsDomain string,
+ eventStore activity.Store,
+ geo *geolocation.Geolocation,
userDeleteFromIDPEnabled bool,
integratedPeerValidator integrated_validator.IntegratedValidator,
+ metrics telemetry.AppMetrics,
) (*DefaultAccountManager, error) {
am := &DefaultAccountManager{
Store: store,
@@ -889,6 +964,7 @@ func BuildManager(ctx context.Context, store Store, peersUpdateManager *PeersUpd
peerLoginExpiry: NewDefaultScheduler(),
userDeleteFromIDPEnabled: userDeleteFromIDPEnabled,
integratedPeerValidator: integratedPeerValidator,
+ metrics: metrics,
}
allAccounts := store.GetAllAccounts(ctx)
// enable single account mode only if configured by user and number of existing accounts is not grater than 1
diff --git a/management/server/account_test.go b/management/server/account_test.go
index 45b4fbd6f5a..03b5fa83efd 100644
--- a/management/server/account_test.go
+++ b/management/server/account_test.go
@@ -24,6 +24,7 @@ import (
"github.com/netbirdio/netbird/management/server/jwtclaims"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/posture"
+ "github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/route"
)
@@ -410,7 +411,8 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
validatedPeers[p] = struct{}{}
}
- networkMap := account.GetPeerNetworkMap(context.Background(), testCase.peerID, "netbird.io", validatedPeers)
+ customZone := account.GetPeersCustomZone(context.Background(), "netbird.io")
+ networkMap := account.GetPeerNetworkMap(context.Background(), testCase.peerID, customZone, validatedPeers, nil)
assert.Len(t, networkMap.Peers, len(testCase.expectedPeers))
assert.Len(t, networkMap.OfflinePeers, len(testCase.expectedOfflinePeers))
}
@@ -2293,7 +2295,13 @@ func TestAccount_UserGroupsRemoveFromPeers(t *testing.T) {
})
}
-func createManager(t *testing.T) (*DefaultAccountManager, error) {
+type TB interface {
+ Cleanup(func())
+ Helper()
+ TempDir() string
+}
+
+func createManager(t TB) (*DefaultAccountManager, error) {
t.Helper()
store, err := createStore(t)
@@ -2302,7 +2310,12 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) {
}
eventStore := &activity.InMemoryEventStore{}
- manager, err := BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{})
+ metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
+ if err != nil {
+ return nil, err
+ }
+
+ manager, err := BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics)
if err != nil {
return nil, err
}
@@ -2310,7 +2323,7 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) {
return manager, nil
}
-func createStore(t *testing.T) (Store, error) {
+func createStore(t TB) (Store, error) {
t.Helper()
dataDir := t.TempDir()
store, cleanUp, err := NewTestStoreFromJson(context.Background(), dataDir)
diff --git a/management/server/dns.go b/management/server/dns.go
index 08732ad78ec..1d156c90a62 100644
--- a/management/server/dns.go
+++ b/management/server/dns.go
@@ -4,8 +4,8 @@ import (
"context"
"fmt"
"strconv"
+ "sync"
- "github.com/miekg/dns"
log "github.com/sirupsen/logrus"
nbdns "github.com/netbirdio/netbird/dns"
@@ -17,6 +17,50 @@ import (
const defaultTTL = 300
+// DNSConfigCache is a thread-safe cache for DNS configuration components
+type DNSConfigCache struct {
+ CustomZones sync.Map
+ NameServerGroups sync.Map
+}
+
+// GetCustomZone retrieves a cached custom zone
+func (c *DNSConfigCache) GetCustomZone(key string) (*proto.CustomZone, bool) {
+ if c == nil {
+ return nil, false
+ }
+ if value, ok := c.CustomZones.Load(key); ok {
+ return value.(*proto.CustomZone), true
+ }
+ return nil, false
+}
+
+// SetCustomZone stores a custom zone in the cache
+func (c *DNSConfigCache) SetCustomZone(key string, value *proto.CustomZone) {
+ if c == nil {
+ return
+ }
+ c.CustomZones.Store(key, value)
+}
+
+// GetNameServerGroup retrieves a cached name server group
+func (c *DNSConfigCache) GetNameServerGroup(key string) (*proto.NameServerGroup, bool) {
+ if c == nil {
+ return nil, false
+ }
+ if value, ok := c.NameServerGroups.Load(key); ok {
+ return value.(*proto.NameServerGroup), true
+ }
+ return nil, false
+}
+
+// SetNameServerGroup stores a name server group in the cache
+func (c *DNSConfigCache) SetNameServerGroup(key string, value *proto.NameServerGroup) {
+ if c == nil {
+ return
+ }
+ c.NameServerGroups.Store(key, value)
+}
+
type lookupMap map[string]struct{}
// DNSSettings defines dns settings at the account level
@@ -113,69 +157,73 @@ func (am *DefaultAccountManager) SaveDNSSettings(ctx context.Context, accountID
return nil
}
-func toProtocolDNSConfig(update nbdns.Config) *proto.DNSConfig {
- protoUpdate := &proto.DNSConfig{ServiceEnable: update.ServiceEnable}
+// toProtocolDNSConfig converts nbdns.Config to proto.DNSConfig using the cache
+func toProtocolDNSConfig(update nbdns.Config, cache *DNSConfigCache) *proto.DNSConfig {
+ protoUpdate := &proto.DNSConfig{
+ ServiceEnable: update.ServiceEnable,
+ CustomZones: make([]*proto.CustomZone, 0, len(update.CustomZones)),
+ NameServerGroups: make([]*proto.NameServerGroup, 0, len(update.NameServerGroups)),
+ }
for _, zone := range update.CustomZones {
- protoZone := &proto.CustomZone{Domain: zone.Domain}
- for _, record := range zone.Records {
- protoZone.Records = append(protoZone.Records, &proto.SimpleRecord{
- Name: record.Name,
- Type: int64(record.Type),
- Class: record.Class,
- TTL: int64(record.TTL),
- RData: record.RData,
- })
+ cacheKey := zone.Domain
+ if cachedZone, exists := cache.GetCustomZone(cacheKey); exists {
+ protoUpdate.CustomZones = append(protoUpdate.CustomZones, cachedZone)
+ } else {
+ protoZone := convertToProtoCustomZone(zone)
+ cache.SetCustomZone(cacheKey, protoZone)
+ protoUpdate.CustomZones = append(protoUpdate.CustomZones, protoZone)
}
- protoUpdate.CustomZones = append(protoUpdate.CustomZones, protoZone)
}
for _, nsGroup := range update.NameServerGroups {
- protoGroup := &proto.NameServerGroup{
- Primary: nsGroup.Primary,
- Domains: nsGroup.Domains,
- SearchDomainsEnabled: nsGroup.SearchDomainsEnabled,
- }
- for _, ns := range nsGroup.NameServers {
- protoNS := &proto.NameServer{
- IP: ns.IP.String(),
- Port: int64(ns.Port),
- NSType: int64(ns.NSType),
- }
- protoGroup.NameServers = append(protoGroup.NameServers, protoNS)
+ cacheKey := nsGroup.ID
+ if cachedGroup, exists := cache.GetNameServerGroup(cacheKey); exists {
+ protoUpdate.NameServerGroups = append(protoUpdate.NameServerGroups, cachedGroup)
+ } else {
+ protoGroup := convertToProtoNameServerGroup(nsGroup)
+ cache.SetNameServerGroup(cacheKey, protoGroup)
+ protoUpdate.NameServerGroups = append(protoUpdate.NameServerGroups, protoGroup)
}
- protoUpdate.NameServerGroups = append(protoUpdate.NameServerGroups, protoGroup)
}
return protoUpdate
}
-func getPeersCustomZone(ctx context.Context, account *Account, dnsDomain string) nbdns.CustomZone {
- if dnsDomain == "" {
- log.WithContext(ctx).Errorf("no dns domain is set, returning empty zone")
- return nbdns.CustomZone{}
+// Helper function to convert nbdns.CustomZone to proto.CustomZone
+func convertToProtoCustomZone(zone nbdns.CustomZone) *proto.CustomZone {
+ protoZone := &proto.CustomZone{
+ Domain: zone.Domain,
+ Records: make([]*proto.SimpleRecord, 0, len(zone.Records)),
}
-
- customZone := nbdns.CustomZone{
- Domain: dns.Fqdn(dnsDomain),
+ for _, record := range zone.Records {
+ protoZone.Records = append(protoZone.Records, &proto.SimpleRecord{
+ Name: record.Name,
+ Type: int64(record.Type),
+ Class: record.Class,
+ TTL: int64(record.TTL),
+ RData: record.RData,
+ })
}
+ return protoZone
+}
- for _, peer := range account.Peers {
- if peer.DNSLabel == "" {
- log.WithContext(ctx).Errorf("found a peer with empty dns label. It was probably caused by a invalid character in its name. Peer Name: %s", peer.Name)
- continue
- }
-
- customZone.Records = append(customZone.Records, nbdns.SimpleRecord{
- Name: dns.Fqdn(peer.DNSLabel + "." + dnsDomain),
- Type: int(dns.TypeA),
- Class: nbdns.DefaultClass,
- TTL: defaultTTL,
- RData: peer.IP.String(),
+// Helper function to convert nbdns.NameServerGroup to proto.NameServerGroup
+func convertToProtoNameServerGroup(nsGroup *nbdns.NameServerGroup) *proto.NameServerGroup {
+ protoGroup := &proto.NameServerGroup{
+ Primary: nsGroup.Primary,
+ Domains: nsGroup.Domains,
+ SearchDomainsEnabled: nsGroup.SearchDomainsEnabled,
+ NameServers: make([]*proto.NameServer, 0, len(nsGroup.NameServers)),
+ }
+ for _, ns := range nsGroup.NameServers {
+ protoGroup.NameServers = append(protoGroup.NameServers, &proto.NameServer{
+ IP: ns.IP.String(),
+ Port: int64(ns.Port),
+ NSType: int64(ns.NSType),
})
}
-
- return customZone
+ return protoGroup
}
func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup {
diff --git a/management/server/dns_test.go b/management/server/dns_test.go
index c6758036f1f..e033c1a214f 100644
--- a/management/server/dns_test.go
+++ b/management/server/dns_test.go
@@ -2,9 +2,14 @@ package server
import (
"context"
+ "fmt"
"net/netip"
+ "reflect"
"testing"
+ nbdns "github.com/netbirdio/netbird/dns"
+ "github.com/netbirdio/netbird/management/server/telemetry"
+
"github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/dns"
@@ -195,7 +200,11 @@ func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
return nil, err
}
eventStore := &activity.InMemoryEventStore{}
- return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false, MocIntegratedValidator{})
+
+ metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
+ require.NoError(t, err)
+
+ return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false, MocIntegratedValidator{}, metrics)
}
func createDNSStore(t *testing.T) (Store, error) {
@@ -320,3 +329,150 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro
return am.Store.GetAccount(context.Background(), account.Id)
}
+
+func generateTestData(size int) nbdns.Config {
+ config := nbdns.Config{
+ ServiceEnable: true,
+ CustomZones: make([]nbdns.CustomZone, size),
+ NameServerGroups: make([]*nbdns.NameServerGroup, size),
+ }
+
+ for i := 0; i < size; i++ {
+ config.CustomZones[i] = nbdns.CustomZone{
+ Domain: fmt.Sprintf("domain%d.com", i),
+ Records: []nbdns.SimpleRecord{
+ {
+ Name: fmt.Sprintf("record%d", i),
+ Type: 1,
+ Class: "IN",
+ TTL: 3600,
+ RData: "192.168.1.1",
+ },
+ },
+ }
+
+ config.NameServerGroups[i] = &nbdns.NameServerGroup{
+ ID: fmt.Sprintf("group%d", i),
+ Primary: i == 0,
+ Domains: []string{fmt.Sprintf("domain%d.com", i)},
+ SearchDomainsEnabled: true,
+ NameServers: []nbdns.NameServer{
+ {
+ IP: netip.MustParseAddr("8.8.8.8"),
+ Port: 53,
+ NSType: 1,
+ },
+ },
+ }
+ }
+
+ return config
+}
+
+func BenchmarkToProtocolDNSConfig(b *testing.B) {
+ sizes := []int{10, 100, 1000}
+
+ for _, size := range sizes {
+ testData := generateTestData(size)
+
+ b.Run(fmt.Sprintf("WithCache-Size%d", size), func(b *testing.B) {
+ cache := &DNSConfigCache{}
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ toProtocolDNSConfig(testData, cache)
+ }
+ })
+
+ b.Run(fmt.Sprintf("WithoutCache-Size%d", size), func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ cache := &DNSConfigCache{}
+ toProtocolDNSConfig(testData, cache)
+ }
+ })
+ }
+}
+
+func TestToProtocolDNSConfigWithCache(t *testing.T) {
+ var cache DNSConfigCache
+
+ // Create two different configs
+ config1 := nbdns.Config{
+ ServiceEnable: true,
+ CustomZones: []nbdns.CustomZone{
+ {
+ Domain: "example.com",
+ Records: []nbdns.SimpleRecord{
+ {Name: "www", Type: 1, Class: "IN", TTL: 300, RData: "192.168.1.1"},
+ },
+ },
+ },
+ NameServerGroups: []*nbdns.NameServerGroup{
+ {
+ ID: "group1",
+ Name: "Group 1",
+ NameServers: []nbdns.NameServer{
+ {IP: netip.MustParseAddr("8.8.8.8"), Port: 53},
+ },
+ },
+ },
+ }
+
+ config2 := nbdns.Config{
+ ServiceEnable: true,
+ CustomZones: []nbdns.CustomZone{
+ {
+ Domain: "example.org",
+ Records: []nbdns.SimpleRecord{
+ {Name: "mail", Type: 1, Class: "IN", TTL: 300, RData: "192.168.1.2"},
+ },
+ },
+ },
+ NameServerGroups: []*nbdns.NameServerGroup{
+ {
+ ID: "group2",
+ Name: "Group 2",
+ NameServers: []nbdns.NameServer{
+ {IP: netip.MustParseAddr("8.8.4.4"), Port: 53},
+ },
+ },
+ },
+ }
+
+ // First run with config1
+ result1 := toProtocolDNSConfig(config1, &cache)
+
+ // Second run with config2
+ result2 := toProtocolDNSConfig(config2, &cache)
+
+ // Third run with config1 again
+ result3 := toProtocolDNSConfig(config1, &cache)
+
+ // Verify that result1 and result3 are identical
+ if !reflect.DeepEqual(result1, result3) {
+ t.Errorf("Results are not identical when run with the same input. Expected %v, got %v", result1, result3)
+ }
+
+ // Verify that result2 is different from result1 and result3
+ if reflect.DeepEqual(result1, result2) || reflect.DeepEqual(result2, result3) {
+ t.Errorf("Results should be different for different inputs")
+ }
+
+ // Verify that the cache contains elements from both configs
+ if _, exists := cache.GetCustomZone("example.com"); !exists {
+ t.Errorf("Cache should contain custom zone for example.com")
+ }
+
+ if _, exists := cache.GetCustomZone("example.org"); !exists {
+ t.Errorf("Cache should contain custom zone for example.org")
+ }
+
+ if _, exists := cache.GetNameServerGroup("group1"); !exists {
+ t.Errorf("Cache should contain name server group 'group1'")
+ }
+
+ if _, exists := cache.GetNameServerGroup("group2"); !exists {
+ t.Errorf("Cache should contain name server group 'group2'")
+ }
+}
diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go
index f71a45d9992..7738abe5ecb 100644
--- a/management/server/grpcserver.go
+++ b/management/server/grpcserver.go
@@ -533,53 +533,46 @@ func toPeerConfig(peer *nbpeer.Peer, network *Network, dnsName string) *proto.Pe
}
}
-func toRemotePeerConfig(peers []*nbpeer.Peer, dnsName string) []*proto.RemotePeerConfig {
- remotePeers := []*proto.RemotePeerConfig{}
- for _, rPeer := range peers {
- fqdn := rPeer.FQDN(dnsName)
- remotePeers = append(remotePeers, &proto.RemotePeerConfig{
- WgPubKey: rPeer.Key,
- AllowedIps: []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)},
- SshConfig: &proto.SSHConfig{SshPubKey: []byte(rPeer.SSHKey)},
- Fqdn: fqdn,
- })
+func toSyncResponse(ctx context.Context, config *Config, peer *nbpeer.Peer, turnCredentials *TURNCredentials, networkMap *NetworkMap, dnsName string, checks []*posture.Checks, dnsCache *DNSConfigCache) *proto.SyncResponse {
+ response := &proto.SyncResponse{
+ WiretrusteeConfig: toWiretrusteeConfig(config, turnCredentials),
+ PeerConfig: toPeerConfig(peer, networkMap.Network, dnsName),
+ NetworkMap: &proto.NetworkMap{
+ Serial: networkMap.Network.CurrentSerial(),
+ Routes: toProtocolRoutes(networkMap.Routes),
+ DNSConfig: toProtocolDNSConfig(networkMap.DNSConfig, dnsCache),
+ },
+ Checks: toProtocolChecks(ctx, checks),
}
- return remotePeers
-}
-
-func toSyncResponse(ctx context.Context, config *Config, peer *nbpeer.Peer, turnCredentials *TURNCredentials, networkMap *NetworkMap, dnsName string, checks []*posture.Checks) *proto.SyncResponse {
- wtConfig := toWiretrusteeConfig(config, turnCredentials)
-
- pConfig := toPeerConfig(peer, networkMap.Network, dnsName)
- remotePeers := toRemotePeerConfig(networkMap.Peers, dnsName)
+ response.NetworkMap.PeerConfig = response.PeerConfig
- routesUpdate := toProtocolRoutes(networkMap.Routes)
+ allPeers := make([]*proto.RemotePeerConfig, 0, len(networkMap.Peers)+len(networkMap.OfflinePeers))
+ allPeers = appendRemotePeerConfig(allPeers, networkMap.Peers, dnsName)
+ response.RemotePeers = allPeers
+ response.NetworkMap.RemotePeers = allPeers
+ response.RemotePeersIsEmpty = len(allPeers) == 0
+ response.NetworkMap.RemotePeersIsEmpty = response.RemotePeersIsEmpty
- dnsUpdate := toProtocolDNSConfig(networkMap.DNSConfig)
-
- offlinePeers := toRemotePeerConfig(networkMap.OfflinePeers, dnsName)
+ response.NetworkMap.OfflinePeers = appendRemotePeerConfig(nil, networkMap.OfflinePeers, dnsName)
firewallRules := toProtocolFirewallRules(networkMap.FirewallRules)
+ response.NetworkMap.FirewallRules = firewallRules
+ response.NetworkMap.FirewallRulesIsEmpty = len(firewallRules) == 0
- return &proto.SyncResponse{
- WiretrusteeConfig: wtConfig,
- PeerConfig: pConfig,
- RemotePeers: remotePeers,
- RemotePeersIsEmpty: len(remotePeers) == 0,
- NetworkMap: &proto.NetworkMap{
- Serial: networkMap.Network.CurrentSerial(),
- PeerConfig: pConfig,
- RemotePeers: remotePeers,
- OfflinePeers: offlinePeers,
- RemotePeersIsEmpty: len(remotePeers) == 0,
- Routes: routesUpdate,
- DNSConfig: dnsUpdate,
- FirewallRules: firewallRules,
- FirewallRulesIsEmpty: len(firewallRules) == 0,
- },
- Checks: toProtocolChecks(ctx, checks),
+ return response
+}
+
+func appendRemotePeerConfig(dst []*proto.RemotePeerConfig, peers []*nbpeer.Peer, dnsName string) []*proto.RemotePeerConfig {
+ for _, rPeer := range peers {
+ dst = append(dst, &proto.RemotePeerConfig{
+ WgPubKey: rPeer.Key,
+ AllowedIps: []string{rPeer.IP.String() + "/32"},
+ SshConfig: &proto.SSHConfig{SshPubKey: []byte(rPeer.SSHKey)},
+ Fqdn: rPeer.FQDN(dnsName),
+ })
}
+ return dst
}
// IsHealthy indicates whether the service is healthy
@@ -597,7 +590,7 @@ func (s *GRPCServer) sendInitialSync(ctx context.Context, peerKey wgtypes.Key, p
} else {
turnCredentials = nil
}
- plainResp := toSyncResponse(ctx, s.config, peer, turnCredentials, networkMap, s.accountManager.GetDNSDomain(), postureChecks)
+ plainResp := toSyncResponse(ctx, s.config, peer, turnCredentials, networkMap, s.accountManager.GetDNSDomain(), postureChecks, nil)
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, plainResp)
if err != nil {
diff --git a/management/server/http/peers_handler.go b/management/server/http/peers_handler.go
index 1fb18669c45..913d424d1b4 100644
--- a/management/server/http/peers_handler.go
+++ b/management/server/http/peers_handler.go
@@ -71,7 +71,8 @@ func (h *PeersHandler) getPeer(ctx context.Context, account *server.Account, pee
return
}
- netMap := account.GetPeerNetworkMap(ctx, peerID, h.accountManager.GetDNSDomain(), validPeers)
+ customZone := account.GetPeersCustomZone(ctx, h.accountManager.GetDNSDomain())
+ netMap := account.GetPeerNetworkMap(ctx, peerID, customZone, validPeers, nil)
accessiblePeers := toAccessiblePeers(netMap, dnsDomain)
_, valid := validPeers[peer.ID]
@@ -115,7 +116,9 @@ func (h *PeersHandler) updatePeer(ctx context.Context, account *server.Account,
util.WriteError(ctx, fmt.Errorf("internal error"), w)
return
}
- netMap := account.GetPeerNetworkMap(ctx, peerID, h.accountManager.GetDNSDomain(), validPeers)
+
+ customZone := account.GetPeersCustomZone(ctx, h.accountManager.GetDNSDomain())
+ netMap := account.GetPeerNetworkMap(ctx, peerID, customZone, validPeers, nil)
accessiblePeers := toAccessiblePeers(netMap, dnsDomain)
_, valid := validPeers[peer.ID]
@@ -194,9 +197,7 @@ func (h *PeersHandler) GetAllPeers(w http.ResponseWriter, r *http.Request) {
}
groupMinimumInfo := toGroupsInfo(account.Groups, peer.ID)
- accessiblePeerNumbers, _ := h.accessiblePeersNumber(r.Context(), account, peer.ID)
-
- respBody = append(respBody, toPeerListItemResponse(peerToReturn, groupMinimumInfo, dnsDomain, accessiblePeerNumbers))
+ respBody = append(respBody, toPeerListItemResponse(peerToReturn, groupMinimumInfo, dnsDomain, 0))
}
validPeersMap, err := h.accountManager.GetValidatedPeers(account)
@@ -210,16 +211,6 @@ func (h *PeersHandler) GetAllPeers(w http.ResponseWriter, r *http.Request) {
util.WriteJSONObject(r.Context(), w, respBody)
}
-func (h *PeersHandler) accessiblePeersNumber(ctx context.Context, account *server.Account, peerID string) (int, error) {
- validatedPeersMap, err := h.accountManager.GetValidatedPeers(account)
- if err != nil {
- return 0, err
- }
-
- netMap := account.GetPeerNetworkMap(ctx, peerID, h.accountManager.GetDNSDomain(), validatedPeersMap)
- return len(netMap.Peers) + len(netMap.OfflinePeers), nil
-}
-
func (h *PeersHandler) setApprovalRequiredFlag(respBody []*api.PeerBatch, approvedPeersMap map[string]struct{}) {
for _, peer := range respBody {
_, ok := approvedPeersMap[peer.Id]
diff --git a/management/server/management_proto_test.go b/management/server/management_proto_test.go
index 2c9d43948f4..fe1e36d47a4 100644
--- a/management/server/management_proto_test.go
+++ b/management/server/management_proto_test.go
@@ -20,6 +20,7 @@ import (
"github.com/netbirdio/netbird/formatter"
mgmtProto "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server/activity"
+ "github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/util"
)
@@ -419,8 +420,12 @@ func startManagement(t *testing.T, config *Config) (*grpc.Server, *DefaultAccoun
ctx := context.WithValue(context.Background(), formatter.ExecutionContextKey, formatter.SystemSource) //nolint:staticcheck
+ metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
+ require.NoError(t, err)
+
accountManager, err := BuildManager(ctx, store, peersUpdateManager, nil, "", "netbird.selfhosted",
- eventStore, nil, false, MocIntegratedValidator{})
+ eventStore, nil, false, MocIntegratedValidator{}, metrics)
+
if err != nil {
return nil, nil, "", err
}
diff --git a/management/server/management_test.go b/management/server/management_test.go
index 092567607da..62e7f5a056a 100644
--- a/management/server/management_test.go
+++ b/management/server/management_test.go
@@ -26,6 +26,7 @@ import (
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/group"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
+ "github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/util"
)
@@ -541,8 +542,13 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) {
peersUpdateManager := server.NewPeersUpdateManager(nil)
eventStore := &activity.InMemoryEventStore{}
- accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted",
- eventStore, nil, false, MocIntegratedValidator{})
+
+ metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
+ if err != nil {
+ log.Fatalf("failed creating metrics: %v", err)
+ }
+
+ accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics)
if err != nil {
log.Fatalf("failed creating a manager: %v", err)
}
diff --git a/management/server/nameserver_test.go b/management/server/nameserver_test.go
index dd7935fee7c..5f8545243a0 100644
--- a/management/server/nameserver_test.go
+++ b/management/server/nameserver_test.go
@@ -11,6 +11,7 @@ import (
"github.com/netbirdio/netbird/management/server/activity"
nbgroup "github.com/netbirdio/netbird/management/server/group"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
+ "github.com/netbirdio/netbird/management/server/telemetry"
)
const (
@@ -762,7 +763,11 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) {
return nil, err
}
eventStore := &activity.InMemoryEventStore{}
- return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{})
+
+ metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
+ require.NoError(t, err)
+
+ return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics)
}
func createNSStore(t *testing.T) (Store, error) {
diff --git a/management/server/peer.go b/management/server/peer.go
index 05fb11236cb..7afe6ee0d4e 100644
--- a/management/server/peer.go
+++ b/management/server/peer.go
@@ -5,6 +5,7 @@ import (
"fmt"
"net"
"strings"
+ "sync"
"time"
"github.com/rs/xid"
@@ -322,7 +323,8 @@ func (am *DefaultAccountManager) GetNetworkMap(ctx context.Context, peerID strin
if err != nil {
return nil, err
}
- return account.GetPeerNetworkMap(ctx, peer.ID, am.dnsDomain, validatedPeers), nil
+ customZone := account.GetPeersCustomZone(ctx, am.dnsDomain)
+ return account.GetPeerNetworkMap(ctx, peer.ID, customZone, validatedPeers, nil), nil
}
// GetPeerNetwork returns the Network for a given peer
@@ -535,7 +537,8 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, setupKey, userID s
}
postureChecks := am.getPeerPostureChecks(account, peer)
- networkMap := account.GetPeerNetworkMap(ctx, newPeer.ID, am.dnsDomain, approvedPeersMap)
+ customZone := account.GetPeersCustomZone(ctx, am.dnsDomain)
+ networkMap := account.GetPeerNetworkMap(ctx, newPeer.ID, customZone, approvedPeersMap, am.metrics.AccountManagerMetrics())
return newPeer, networkMap, postureChecks, nil
}
@@ -591,7 +594,8 @@ func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync PeerSync, ac
}
postureChecks = am.getPeerPostureChecks(account, peer)
- return peer, account.GetPeerNetworkMap(ctx, peer.ID, am.dnsDomain, validPeersMap), postureChecks, nil
+ customZone := account.GetPeersCustomZone(ctx, am.dnsDomain)
+ return peer, account.GetPeerNetworkMap(ctx, peer.ID, customZone, validPeersMap, am.metrics.AccountManagerMetrics()), postureChecks, nil
}
// LoginPeer logs in or registers a peer.
@@ -738,7 +742,8 @@ func (am *DefaultAccountManager) getValidatedPeerWithMap(ctx context.Context, is
}
postureChecks = am.getPeerPostureChecks(account, peer)
- return peer, account.GetPeerNetworkMap(ctx, peer.ID, am.dnsDomain, approvedPeersMap), postureChecks, nil
+ customZone := account.GetPeersCustomZone(ctx, am.dnsDomain)
+ return peer, account.GetPeerNetworkMap(ctx, peer.ID, customZone, approvedPeersMap, am.metrics.AccountManagerMetrics()), postureChecks, nil
}
func (am *DefaultAccountManager) handleExpiredPeer(ctx context.Context, login PeerLogin, account *Account, peer *nbpeer.Peer) error {
@@ -914,22 +919,45 @@ func updatePeerMeta(peer *nbpeer.Peer, meta nbpeer.PeerSystemMeta, account *Acco
// updateAccountPeers updates all peers that belong to an account.
// Should be called when changes have to be synced to peers.
func (am *DefaultAccountManager) updateAccountPeers(ctx context.Context, account *Account) {
+ start := time.Now()
+ defer func() {
+ if am.metrics != nil {
+ am.metrics.AccountManagerMetrics().CountUpdateAccountPeersDuration(time.Since(start))
+ }
+ }()
+
peers := account.GetPeers()
approvedPeersMap, err := am.GetValidatedPeers(account)
if err != nil {
- log.WithContext(ctx).Errorf("failed send out updates to peers, failed to validate peer: %v", err)
+ log.WithContext(ctx).Errorf("failed to send out updates to peers, failed to validate peer: %v", err)
return
}
+
+ var wg sync.WaitGroup
+ semaphore := make(chan struct{}, 10)
+
+ dnsCache := &DNSConfigCache{}
+ customZone := account.GetPeersCustomZone(ctx, am.dnsDomain)
+
for _, peer := range peers {
if !am.peersUpdateManager.HasChannel(peer.ID) {
log.WithContext(ctx).Tracef("peer %s doesn't have a channel, skipping network map update", peer.ID)
continue
}
- postureChecks := am.getPeerPostureChecks(account, peer)
- remotePeerNetworkMap := account.GetPeerNetworkMap(ctx, peer.ID, am.dnsDomain, approvedPeersMap)
- update := toSyncResponse(ctx, nil, peer, nil, remotePeerNetworkMap, am.GetDNSDomain(), postureChecks)
- am.peersUpdateManager.SendUpdate(ctx, peer.ID, &UpdateMessage{Update: update})
+ wg.Add(1)
+ semaphore <- struct{}{}
+ go func(p *nbpeer.Peer) {
+ defer wg.Done()
+ defer func() { <-semaphore }()
+
+ postureChecks := am.getPeerPostureChecks(account, p)
+ remotePeerNetworkMap := account.GetPeerNetworkMap(ctx, p.ID, customZone, approvedPeersMap, am.metrics.AccountManagerMetrics())
+ update := toSyncResponse(ctx, nil, p, nil, remotePeerNetworkMap, am.GetDNSDomain(), postureChecks, dnsCache)
+ am.peersUpdateManager.SendUpdate(ctx, p.ID, &UpdateMessage{Update: update})
+ }(peer)
}
+
+ wg.Wait()
}
diff --git a/management/server/peer/peer.go b/management/server/peer/peer.go
index 4f808a79eea..3d9ba18e9e5 100644
--- a/management/server/peer/peer.go
+++ b/management/server/peer/peer.go
@@ -1,7 +1,6 @@
package peer
import (
- "fmt"
"net"
"net/netip"
"slices"
@@ -241,7 +240,7 @@ func (p *Peer) FQDN(dnsDomain string) string {
if dnsDomain == "" {
return ""
}
- return fmt.Sprintf("%s.%s", p.DNSLabel, dnsDomain)
+ return p.DNSLabel + "." + dnsDomain
}
// EventMeta returns activity event meta related to the peer
diff --git a/management/server/peer/peer_test.go b/management/server/peer/peer_test.go
new file mode 100644
index 00000000000..7b94f68c67f
--- /dev/null
+++ b/management/server/peer/peer_test.go
@@ -0,0 +1,31 @@
+package peer
+
+import (
+ "fmt"
+ "testing"
+)
+
+// FQDNOld is the original implementation for benchmarking purposes
+func (p *Peer) FQDNOld(dnsDomain string) string {
+ if dnsDomain == "" {
+ return ""
+ }
+ return fmt.Sprintf("%s.%s", p.DNSLabel, dnsDomain)
+}
+
+func BenchmarkFQDN(b *testing.B) {
+ p := &Peer{DNSLabel: "test-peer"}
+ dnsDomain := "example.com"
+
+ b.Run("Old", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ p.FQDNOld(dnsDomain)
+ }
+ })
+
+ b.Run("New", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ p.FQDN(dnsDomain)
+ }
+ })
+}
diff --git a/management/server/peer_test.go b/management/server/peer_test.go
index 407877296b5..91843651563 100644
--- a/management/server/peer_test.go
+++ b/management/server/peer_test.go
@@ -2,15 +2,26 @@ package server
import (
"context"
+ "fmt"
+ "io"
+ "net"
+ "net/netip"
+ "os"
"testing"
"time"
"github.com/rs/xid"
+ log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+ nbdns "github.com/netbirdio/netbird/dns"
+ "github.com/netbirdio/netbird/management/domain"
+ "github.com/netbirdio/netbird/management/proto"
nbgroup "github.com/netbirdio/netbird/management/server/group"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
+ "github.com/netbirdio/netbird/management/server/posture"
+ nbroute "github.com/netbirdio/netbird/route"
)
func TestPeer_LoginExpired(t *testing.T) {
@@ -633,3 +644,354 @@ func TestDefaultAccountManager_GetPeers(t *testing.T) {
}
}
+
+func setupTestAccountManager(b *testing.B, peers int, groups int) (*DefaultAccountManager, string, string, error) {
+ b.Helper()
+
+ manager, err := createManager(b)
+ if err != nil {
+ return nil, "", "", err
+ }
+
+ accountID := "test_account"
+ adminUser := "account_creator"
+ regularUser := "regular_user"
+
+ account := newAccountWithId(context.Background(), accountID, adminUser, "")
+ account.Users[regularUser] = &User{
+ Id: regularUser,
+ Role: UserRoleUser,
+ }
+
+ // Create peers
+ for i := 0; i < peers; i++ {
+ peerKey, _ := wgtypes.GeneratePrivateKey()
+ peer := &nbpeer.Peer{
+ ID: fmt.Sprintf("peer-%d", i),
+ DNSLabel: fmt.Sprintf("peer-%d", i),
+ Key: peerKey.PublicKey().String(),
+ IP: net.ParseIP(fmt.Sprintf("100.64.%d.%d", i/256, i%256)),
+ Status: &nbpeer.PeerStatus{},
+ UserID: regularUser,
+ }
+ account.Peers[peer.ID] = peer
+ }
+
+ // Create groups and policies
+ account.Policies = make([]*Policy, 0, groups)
+ for i := 0; i < groups; i++ {
+ groupID := fmt.Sprintf("group-%d", i)
+ group := &nbgroup.Group{
+ ID: groupID,
+ Name: fmt.Sprintf("Group %d", i),
+ }
+ for j := 0; j < peers/groups; j++ {
+ peerIndex := i*(peers/groups) + j
+ group.Peers = append(group.Peers, fmt.Sprintf("peer-%d", peerIndex))
+ }
+ account.Groups[groupID] = group
+
+ // Create a policy for this group
+ policy := &Policy{
+ ID: fmt.Sprintf("policy-%d", i),
+ Name: fmt.Sprintf("Policy for Group %d", i),
+ Enabled: true,
+ Rules: []*PolicyRule{
+ {
+ ID: fmt.Sprintf("rule-%d", i),
+ Name: fmt.Sprintf("Rule for Group %d", i),
+ Enabled: true,
+ Sources: []string{groupID},
+ Destinations: []string{groupID},
+ Bidirectional: true,
+ Protocol: PolicyRuleProtocolALL,
+ Action: PolicyTrafficActionAccept,
+ },
+ },
+ }
+ account.Policies = append(account.Policies, policy)
+ }
+
+ account.PostureChecks = []*posture.Checks{
+ {
+ ID: "PostureChecksAll",
+ Name: "All",
+ Checks: posture.ChecksDefinition{
+ NBVersionCheck: &posture.NBVersionCheck{
+ MinVersion: "0.0.1",
+ },
+ },
+ },
+ }
+
+ err = manager.Store.SaveAccount(context.Background(), account)
+ if err != nil {
+ return nil, "", "", err
+ }
+
+ return manager, accountID, regularUser, nil
+}
+
+func BenchmarkGetPeers(b *testing.B) {
+ benchCases := []struct {
+ name string
+ peers int
+ groups int
+ }{
+ {"Small", 50, 5},
+ {"Medium", 500, 10},
+ {"Large", 5000, 20},
+ {"Small single", 50, 1},
+ {"Medium single", 500, 1},
+ {"Large 5", 5000, 5},
+ }
+
+ log.SetOutput(io.Discard)
+ defer log.SetOutput(os.Stderr)
+ for _, bc := range benchCases {
+ b.Run(bc.name, func(b *testing.B) {
+ manager, accountID, userID, err := setupTestAccountManager(b, bc.peers, bc.groups)
+ if err != nil {
+ b.Fatalf("Failed to setup test account manager: %v", err)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := manager.GetPeers(context.Background(), accountID, userID)
+ if err != nil {
+ b.Fatalf("GetPeers failed: %v", err)
+ }
+ }
+ })
+ }
+}
+
+func BenchmarkUpdateAccountPeers(b *testing.B) {
+ benchCases := []struct {
+ name string
+ peers int
+ groups int
+ }{
+ {"Small", 50, 5},
+ {"Medium", 500, 10},
+ {"Large", 5000, 20},
+ {"Small single", 50, 1},
+ {"Medium single", 500, 1},
+ {"Large 5", 5000, 5},
+ }
+
+ log.SetOutput(io.Discard)
+ defer log.SetOutput(os.Stderr)
+
+ for _, bc := range benchCases {
+ b.Run(bc.name, func(b *testing.B) {
+ manager, accountID, _, err := setupTestAccountManager(b, bc.peers, bc.groups)
+ if err != nil {
+ b.Fatalf("Failed to setup test account manager: %v", err)
+ }
+
+ ctx := context.Background()
+
+ account, err := manager.Store.GetAccount(ctx, accountID)
+ if err != nil {
+ b.Fatalf("Failed to get account: %v", err)
+ }
+
+ peerChannels := make(map[string]chan *UpdateMessage)
+
+ for peerID := range account.Peers {
+ peerChannels[peerID] = make(chan *UpdateMessage, channelBufferSize)
+ }
+
+ manager.peersUpdateManager.peerChannels = peerChannels
+
+ b.ResetTimer()
+ start := time.Now()
+
+ for i := 0; i < b.N; i++ {
+ manager.updateAccountPeers(ctx, account)
+ }
+
+ duration := time.Since(start)
+ b.ReportMetric(float64(duration.Nanoseconds())/float64(b.N)/1e6, "ms/op")
+ b.ReportMetric(0, "ns/op")
+ })
+ }
+}
+
+func TestToSyncResponse(t *testing.T) {
+ _, ipnet, err := net.ParseCIDR("192.168.1.0/24")
+ if err != nil {
+ t.Fatal(err)
+ }
+ domainList, err := domain.FromStringList([]string{"example.com"})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ config := &Config{
+ Signal: &Host{
+ Proto: "https",
+ URI: "signal.uri",
+ Username: "",
+ Password: "",
+ },
+ Stuns: []*Host{{URI: "stun.uri", Proto: UDP}},
+ TURNConfig: &TURNConfig{
+ Turns: []*Host{{URI: "turn.uri", Proto: UDP, Username: "turn-user", Password: "turn-pass"}},
+ },
+ }
+ peer := &nbpeer.Peer{
+ IP: net.ParseIP("192.168.1.1"),
+ SSHEnabled: true,
+ Key: "peer-key",
+ DNSLabel: "peer1",
+ SSHKey: "peer1-ssh-key",
+ }
+ turnCredentials := &TURNCredentials{
+ Username: "turn-user",
+ Password: "turn-pass",
+ }
+ networkMap := &NetworkMap{
+ Network: &Network{Net: *ipnet, Serial: 1000},
+ Peers: []*nbpeer.Peer{{IP: net.ParseIP("192.168.1.2"), Key: "peer2-key", DNSLabel: "peer2", SSHEnabled: true, SSHKey: "peer2-ssh-key"}},
+ OfflinePeers: []*nbpeer.Peer{{IP: net.ParseIP("192.168.1.3"), Key: "peer3-key", DNSLabel: "peer3", SSHEnabled: true, SSHKey: "peer3-ssh-key"}},
+ Routes: []*nbroute.Route{
+ {
+ ID: "route1",
+ Network: netip.MustParsePrefix("10.0.0.0/24"),
+ Domains: domainList,
+ KeepRoute: true,
+ NetID: "route1",
+ Peer: "peer1",
+ NetworkType: 1,
+ Masquerade: true,
+ Metric: 9999,
+ Enabled: true,
+ },
+ },
+ DNSConfig: nbdns.Config{
+ ServiceEnable: true,
+ NameServerGroups: []*nbdns.NameServerGroup{
+ {
+ NameServers: []nbdns.NameServer{{
+ IP: netip.MustParseAddr("8.8.8.8"),
+ NSType: nbdns.UDPNameServerType,
+ Port: nbdns.DefaultDNSPort,
+ }},
+ Primary: true,
+ Domains: []string{"example.com"},
+ Enabled: true,
+ SearchDomainsEnabled: true,
+ },
+ {
+ ID: "ns1",
+ NameServers: []nbdns.NameServer{{
+ IP: netip.MustParseAddr("1.1.1.1"),
+ NSType: nbdns.UDPNameServerType,
+ Port: nbdns.DefaultDNSPort,
+ }},
+ Groups: []string{"group1"},
+ Primary: true,
+ Domains: []string{"example.com"},
+ Enabled: true,
+ SearchDomainsEnabled: true,
+ },
+ },
+ CustomZones: []nbdns.CustomZone{{Domain: "example.com", Records: []nbdns.SimpleRecord{{Name: "example.com", Type: 1, Class: "IN", TTL: 60, RData: "100.64.0.1"}}}},
+ },
+ FirewallRules: []*FirewallRule{
+ {PeerIP: "192.168.1.2", Direction: firewallRuleDirectionIN, Action: string(PolicyTrafficActionAccept), Protocol: string(PolicyRuleProtocolTCP), Port: "80"},
+ },
+ }
+ dnsName := "example.com"
+ checks := []*posture.Checks{
+ {
+ Checks: posture.ChecksDefinition{
+ ProcessCheck: &posture.ProcessCheck{
+ Processes: []posture.Process{{LinuxPath: "/usr/bin/netbird"}},
+ },
+ },
+ },
+ }
+ dnsCache := &DNSConfigCache{}
+
+ response := toSyncResponse(context.Background(), config, peer, turnCredentials, networkMap, dnsName, checks, dnsCache)
+
+ assert.NotNil(t, response)
+ // assert peer config
+ assert.Equal(t, "192.168.1.1/24", response.PeerConfig.Address)
+ assert.Equal(t, "peer1.example.com", response.PeerConfig.Fqdn)
+ assert.Equal(t, true, response.PeerConfig.SshConfig.SshEnabled)
+ // assert wiretrustee config
+ assert.Equal(t, "signal.uri", response.WiretrusteeConfig.Signal.Uri)
+ assert.Equal(t, proto.HostConfig_HTTPS, response.WiretrusteeConfig.Signal.GetProtocol())
+ assert.Equal(t, "stun.uri", response.WiretrusteeConfig.Stuns[0].Uri)
+ assert.Equal(t, "turn.uri", response.WiretrusteeConfig.Turns[0].HostConfig.GetUri())
+ assert.Equal(t, "turn-user", response.WiretrusteeConfig.Turns[0].User)
+ assert.Equal(t, "turn-pass", response.WiretrusteeConfig.Turns[0].Password)
+ // assert RemotePeers
+ assert.Equal(t, 1, len(response.RemotePeers))
+ assert.Equal(t, "192.168.1.2/32", response.RemotePeers[0].AllowedIps[0])
+ assert.Equal(t, "peer2-key", response.RemotePeers[0].WgPubKey)
+ assert.Equal(t, "peer2.example.com", response.RemotePeers[0].GetFqdn())
+ assert.Equal(t, false, response.RemotePeers[0].GetSshConfig().GetSshEnabled())
+ assert.Equal(t, []byte("peer2-ssh-key"), response.RemotePeers[0].GetSshConfig().GetSshPubKey())
+ // assert network map
+ assert.Equal(t, uint64(1000), response.NetworkMap.Serial)
+ assert.Equal(t, "192.168.1.1/24", response.NetworkMap.PeerConfig.Address)
+ assert.Equal(t, "peer1.example.com", response.NetworkMap.PeerConfig.Fqdn)
+ assert.Equal(t, true, response.NetworkMap.PeerConfig.SshConfig.SshEnabled)
+ // assert network map RemotePeers
+ assert.Equal(t, 1, len(response.NetworkMap.RemotePeers))
+ assert.Equal(t, "192.168.1.2/32", response.NetworkMap.RemotePeers[0].AllowedIps[0])
+ assert.Equal(t, "peer2-key", response.NetworkMap.RemotePeers[0].WgPubKey)
+ assert.Equal(t, "peer2.example.com", response.NetworkMap.RemotePeers[0].GetFqdn())
+ assert.Equal(t, []byte("peer2-ssh-key"), response.NetworkMap.RemotePeers[0].GetSshConfig().GetSshPubKey())
+ // assert network map OfflinePeers
+ assert.Equal(t, 1, len(response.NetworkMap.OfflinePeers))
+ assert.Equal(t, "192.168.1.3/32", response.NetworkMap.OfflinePeers[0].AllowedIps[0])
+ assert.Equal(t, "peer3-key", response.NetworkMap.OfflinePeers[0].WgPubKey)
+ assert.Equal(t, "peer3.example.com", response.NetworkMap.OfflinePeers[0].GetFqdn())
+ assert.Equal(t, []byte("peer3-ssh-key"), response.NetworkMap.OfflinePeers[0].GetSshConfig().GetSshPubKey())
+ // assert network map Routes
+ assert.Equal(t, 1, len(response.NetworkMap.Routes))
+ assert.Equal(t, "10.0.0.0/24", response.NetworkMap.Routes[0].Network)
+ assert.Equal(t, "route1", response.NetworkMap.Routes[0].ID)
+ assert.Equal(t, "peer1", response.NetworkMap.Routes[0].Peer)
+ assert.Equal(t, "example.com", response.NetworkMap.Routes[0].Domains[0])
+ assert.Equal(t, true, response.NetworkMap.Routes[0].KeepRoute)
+ assert.Equal(t, true, response.NetworkMap.Routes[0].Masquerade)
+ assert.Equal(t, int64(9999), response.NetworkMap.Routes[0].Metric)
+ assert.Equal(t, int64(1), response.NetworkMap.Routes[0].NetworkType)
+ assert.Equal(t, "route1", response.NetworkMap.Routes[0].NetID)
+ // assert network map DNSConfig
+ assert.Equal(t, true, response.NetworkMap.DNSConfig.ServiceEnable)
+ assert.Equal(t, 1, len(response.NetworkMap.DNSConfig.CustomZones))
+ assert.Equal(t, 2, len(response.NetworkMap.DNSConfig.NameServerGroups))
+ // assert network map DNSConfig.CustomZones
+ assert.Equal(t, "example.com", response.NetworkMap.DNSConfig.CustomZones[0].Domain)
+ assert.Equal(t, 1, len(response.NetworkMap.DNSConfig.CustomZones[0].Records))
+ assert.Equal(t, "example.com", response.NetworkMap.DNSConfig.CustomZones[0].Records[0].Name)
+ assert.Equal(t, int64(1), response.NetworkMap.DNSConfig.CustomZones[0].Records[0].Type)
+ assert.Equal(t, "IN", response.NetworkMap.DNSConfig.CustomZones[0].Records[0].Class)
+ assert.Equal(t, int64(60), response.NetworkMap.DNSConfig.CustomZones[0].Records[0].TTL)
+ assert.Equal(t, "100.64.0.1", response.NetworkMap.DNSConfig.CustomZones[0].Records[0].RData)
+ // assert network map DNSConfig.NameServerGroups
+ assert.Equal(t, true, response.NetworkMap.DNSConfig.NameServerGroups[0].Primary)
+ assert.Equal(t, true, response.NetworkMap.DNSConfig.NameServerGroups[0].SearchDomainsEnabled)
+ assert.Equal(t, "example.com", response.NetworkMap.DNSConfig.NameServerGroups[0].Domains[0])
+ assert.Equal(t, "8.8.8.8", response.NetworkMap.DNSConfig.NameServerGroups[0].NameServers[0].GetIP())
+ assert.Equal(t, int64(1), response.NetworkMap.DNSConfig.NameServerGroups[0].NameServers[0].GetNSType())
+ assert.Equal(t, int64(53), response.NetworkMap.DNSConfig.NameServerGroups[0].NameServers[0].GetPort())
+ // assert network map Firewall
+ assert.Equal(t, 1, len(response.NetworkMap.FirewallRules))
+ assert.Equal(t, "192.168.1.2", response.NetworkMap.FirewallRules[0].PeerIP)
+ assert.Equal(t, proto.FirewallRule_IN, response.NetworkMap.FirewallRules[0].Direction)
+ assert.Equal(t, proto.FirewallRule_ACCEPT, response.NetworkMap.FirewallRules[0].Action)
+ assert.Equal(t, proto.FirewallRule_TCP, response.NetworkMap.FirewallRules[0].Protocol)
+ assert.Equal(t, "80", response.NetworkMap.FirewallRules[0].Port)
+ // assert posture checks
+ assert.Equal(t, 1, len(response.Checks))
+ assert.Equal(t, "/usr/bin/netbird", response.Checks[0].Files[0])
+}
diff --git a/management/server/policy.go b/management/server/policy.go
index 30614ed2dd5..aaf9b6e72d0 100644
--- a/management/server/policy.go
+++ b/management/server/policy.go
@@ -213,7 +213,6 @@ type FirewallRule struct {
//
// This function returns the list of peers and firewall rules that are applicable to a given peer.
func (a *Account) getPeerConnectionResources(ctx context.Context, peerID string, validatedPeersMap map[string]struct{}) ([]*nbpeer.Peer, []*FirewallRule) {
-
generateResources, getAccumulatedResources := a.connResourcesGenerator(ctx)
for _, policy := range a.Policies {
if !policy.Enabled {
@@ -225,8 +224,8 @@ func (a *Account) getPeerConnectionResources(ctx context.Context, peerID string,
continue
}
- sourcePeers, peerInSources := getAllPeersFromGroups(ctx, a, rule.Sources, peerID, policy.SourcePostureChecks, validatedPeersMap)
- destinationPeers, peerInDestinations := getAllPeersFromGroups(ctx, a, rule.Destinations, peerID, nil, validatedPeersMap)
+ sourcePeers, peerInSources := a.getAllPeersFromGroups(ctx, rule.Sources, peerID, policy.SourcePostureChecks, validatedPeersMap)
+ destinationPeers, peerInDestinations := a.getAllPeersFromGroups(ctx, rule.Destinations, peerID, nil, validatedPeersMap)
if rule.Bidirectional {
if peerInSources {
@@ -290,8 +289,8 @@ func (a *Account) connResourcesGenerator(ctx context.Context) (func(*PolicyRule,
fr.PeerIP = "0.0.0.0"
}
- ruleID := (rule.ID + fr.PeerIP + strconv.Itoa(direction) +
- fr.Protocol + fr.Action + strings.Join(rule.Ports, ","))
+ ruleID := rule.ID + fr.PeerIP + strconv.Itoa(direction) +
+ fr.Protocol + fr.Action + strings.Join(rule.Ports, ",")
if _, ok := rulesExists[ruleID]; ok {
continue
}
@@ -491,23 +490,23 @@ func toProtocolFirewallRules(update []*FirewallRule) []*proto.FirewallRule {
//
// Important: Posture checks are applicable only to source group peers,
// for destination group peers, call this method with an empty list of sourcePostureChecksIDs
-func getAllPeersFromGroups(ctx context.Context, account *Account, groups []string, peerID string, sourcePostureChecksIDs []string, validatedPeersMap map[string]struct{}) ([]*nbpeer.Peer, bool) {
+func (a *Account) getAllPeersFromGroups(ctx context.Context, groups []string, peerID string, sourcePostureChecksIDs []string, validatedPeersMap map[string]struct{}) ([]*nbpeer.Peer, bool) {
peerInGroups := false
filteredPeers := make([]*nbpeer.Peer, 0, len(groups))
for _, g := range groups {
- group, ok := account.Groups[g]
+ group, ok := a.Groups[g]
if !ok {
continue
}
for _, p := range group.Peers {
- peer, ok := account.Peers[p]
+ peer, ok := a.Peers[p]
if !ok || peer == nil {
continue
}
// validate the peer based on policy posture checks applied
- isValid := account.validatePostureChecksOnPeer(ctx, sourcePostureChecksIDs, peer.ID)
+ isValid := a.validatePostureChecksOnPeer(ctx, sourcePostureChecksIDs, peer.ID)
if !isValid {
continue
}
@@ -535,7 +534,7 @@ func (a *Account) validatePostureChecksOnPeer(ctx context.Context, sourcePosture
}
for _, postureChecksID := range sourcePostureChecksID {
- postureChecks := getPostureChecks(a, postureChecksID)
+ postureChecks := a.getPostureChecks(postureChecksID)
if postureChecks == nil {
continue
}
@@ -553,8 +552,8 @@ func (a *Account) validatePostureChecksOnPeer(ctx context.Context, sourcePosture
return true
}
-func getPostureChecks(account *Account, postureChecksID string) *posture.Checks {
- for _, postureChecks := range account.PostureChecks {
+func (a *Account) getPostureChecks(postureChecksID string) *posture.Checks {
+ for _, postureChecks := range a.PostureChecks {
if postureChecks.ID == postureChecksID {
return postureChecks
}
diff --git a/management/server/route_test.go b/management/server/route_test.go
index 8b168a79fb1..47dc4d07883 100644
--- a/management/server/route_test.go
+++ b/management/server/route_test.go
@@ -13,6 +13,7 @@ import (
"github.com/netbirdio/netbird/management/server/activity"
nbgroup "github.com/netbirdio/netbird/management/server/group"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
+ "github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/route"
)
@@ -1233,7 +1234,11 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) {
return nil, err
}
eventStore := &activity.InMemoryEventStore{}
- return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{})
+
+ metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
+ require.NoError(t, err)
+
+ return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics)
}
func createRouterStore(t *testing.T) (Store, error) {
diff --git a/management/server/telemetry/accountmanager_metrics.go b/management/server/telemetry/accountmanager_metrics.go
new file mode 100644
index 00000000000..e4bb4e3c336
--- /dev/null
+++ b/management/server/telemetry/accountmanager_metrics.go
@@ -0,0 +1,69 @@
+package telemetry
+
+import (
+ "context"
+ "time"
+
+ "go.opentelemetry.io/otel/metric"
+)
+
+// AccountManagerMetrics represents all metrics related to the AccountManager
+type AccountManagerMetrics struct {
+ ctx context.Context
+ updateAccountPeersDurationMs metric.Float64Histogram
+ getPeerNetworkMapDurationMs metric.Float64Histogram
+ networkMapObjectCount metric.Int64Histogram
+}
+
+// NewAccountManagerMetrics creates an instance of AccountManagerMetrics
+func NewAccountManagerMetrics(ctx context.Context, meter metric.Meter) (*AccountManagerMetrics, error) {
+ updateAccountPeersDurationMs, err := meter.Float64Histogram("management.account.update.account.peers.duration.ms",
+ metric.WithUnit("milliseconds"),
+ metric.WithExplicitBucketBoundaries(
+ 0.5, 1, 2.5, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000,
+ ))
+ if err != nil {
+ return nil, err
+ }
+
+ getPeerNetworkMapDurationMs, err := meter.Float64Histogram("management.account.get.peer.network.map.duration.ms",
+ metric.WithUnit("milliseconds"),
+ metric.WithExplicitBucketBoundaries(
+ 0.1, 0.5, 1, 2.5, 5, 10, 25, 50, 100, 250, 500, 1000,
+ ))
+ if err != nil {
+ return nil, err
+ }
+
+ networkMapObjectCount, err := meter.Int64Histogram("management.account.network.map.object.count",
+ metric.WithUnit("objects"),
+ metric.WithExplicitBucketBoundaries(
+ 50, 100, 200, 500, 1000, 2500, 5000, 10000,
+ ))
+ if err != nil {
+ return nil, err
+ }
+
+ return &AccountManagerMetrics{
+ ctx: ctx,
+ getPeerNetworkMapDurationMs: getPeerNetworkMapDurationMs,
+ updateAccountPeersDurationMs: updateAccountPeersDurationMs,
+ networkMapObjectCount: networkMapObjectCount,
+ }, nil
+
+}
+
+// CountUpdateAccountPeersDuration counts the duration of updating account peers
+func (metrics *AccountManagerMetrics) CountUpdateAccountPeersDuration(duration time.Duration) {
+ metrics.updateAccountPeersDurationMs.Record(metrics.ctx, float64(duration.Nanoseconds())/1e6)
+}
+
+// CountGetPeerNetworkMapDuration counts the duration of getting the peer network map
+func (metrics *AccountManagerMetrics) CountGetPeerNetworkMapDuration(duration time.Duration) {
+ metrics.getPeerNetworkMapDurationMs.Record(metrics.ctx, float64(duration.Nanoseconds())/1e6)
+}
+
+// CountNetworkMapObjects counts the number of network map objects
+func (metrics *AccountManagerMetrics) CountNetworkMapObjects(count int64) {
+ metrics.networkMapObjectCount.Record(metrics.ctx, count)
+}
diff --git a/management/server/telemetry/app_metrics.go b/management/server/telemetry/app_metrics.go
index d88e18d8af5..09deb8127db 100644
--- a/management/server/telemetry/app_metrics.go
+++ b/management/server/telemetry/app_metrics.go
@@ -20,14 +20,15 @@ const defaultEndpoint = "/metrics"
// MockAppMetrics mocks the AppMetrics interface
type MockAppMetrics struct {
- GetMeterFunc func() metric2.Meter
- CloseFunc func() error
- ExposeFunc func(ctx context.Context, port int, endpoint string) error
- IDPMetricsFunc func() *IDPMetrics
- HTTPMiddlewareFunc func() *HTTPMiddleware
- GRPCMetricsFunc func() *GRPCMetrics
- StoreMetricsFunc func() *StoreMetrics
- UpdateChannelMetricsFunc func() *UpdateChannelMetrics
+ GetMeterFunc func() metric2.Meter
+ CloseFunc func() error
+ ExposeFunc func(ctx context.Context, port int, endpoint string) error
+ IDPMetricsFunc func() *IDPMetrics
+ HTTPMiddlewareFunc func() *HTTPMiddleware
+ GRPCMetricsFunc func() *GRPCMetrics
+ StoreMetricsFunc func() *StoreMetrics
+ UpdateChannelMetricsFunc func() *UpdateChannelMetrics
+ AddAccountManagerMetricsFunc func() *AccountManagerMetrics
}
// GetMeter mocks the GetMeter function of the AppMetrics interface
@@ -94,6 +95,14 @@ func (mock *MockAppMetrics) UpdateChannelMetrics() *UpdateChannelMetrics {
return nil
}
+// AccountManagerMetrics mocks the MockAppMetrics function of the AccountManagerMetrics interface
+func (mock *MockAppMetrics) AccountManagerMetrics() *AccountManagerMetrics {
+ if mock.AddAccountManagerMetricsFunc != nil {
+ return mock.AddAccountManagerMetricsFunc()
+ }
+ return nil
+}
+
// AppMetrics is metrics interface
type AppMetrics interface {
GetMeter() metric2.Meter
@@ -104,19 +113,21 @@ type AppMetrics interface {
GRPCMetrics() *GRPCMetrics
StoreMetrics() *StoreMetrics
UpdateChannelMetrics() *UpdateChannelMetrics
+ AccountManagerMetrics() *AccountManagerMetrics
}
// defaultAppMetrics are core application metrics based on OpenTelemetry https://opentelemetry.io/
type defaultAppMetrics struct {
// Meter can be used by different application parts to create counters and measure things
- Meter metric2.Meter
- listener net.Listener
- ctx context.Context
- idpMetrics *IDPMetrics
- httpMiddleware *HTTPMiddleware
- grpcMetrics *GRPCMetrics
- storeMetrics *StoreMetrics
- updateChannelMetrics *UpdateChannelMetrics
+ Meter metric2.Meter
+ listener net.Listener
+ ctx context.Context
+ idpMetrics *IDPMetrics
+ httpMiddleware *HTTPMiddleware
+ grpcMetrics *GRPCMetrics
+ storeMetrics *StoreMetrics
+ updateChannelMetrics *UpdateChannelMetrics
+ accountManagerMetrics *AccountManagerMetrics
}
// IDPMetrics returns metrics for the idp package
@@ -144,6 +155,11 @@ func (appMetrics *defaultAppMetrics) UpdateChannelMetrics() *UpdateChannelMetric
return appMetrics.updateChannelMetrics
}
+// AccountManagerMetrics returns metrics for the account manager
+func (appMetrics *defaultAppMetrics) AccountManagerMetrics() *AccountManagerMetrics {
+ return appMetrics.accountManagerMetrics
+}
+
// Close stop application metrics HTTP handler and closes listener.
func (appMetrics *defaultAppMetrics) Close() error {
if appMetrics.listener == nil {
@@ -220,13 +236,19 @@ func NewDefaultAppMetrics(ctx context.Context) (AppMetrics, error) {
return nil, err
}
+ accountManagerMetrics, err := NewAccountManagerMetrics(ctx, meter)
+ if err != nil {
+ return nil, err
+ }
+
return &defaultAppMetrics{
- Meter: meter,
- ctx: ctx,
- idpMetrics: idpMetrics,
- httpMiddleware: middleware,
- grpcMetrics: grpcMetrics,
- storeMetrics: storeMetrics,
- updateChannelMetrics: updateChannelMetrics,
+ Meter: meter,
+ ctx: ctx,
+ idpMetrics: idpMetrics,
+ httpMiddleware: middleware,
+ grpcMetrics: grpcMetrics,
+ storeMetrics: storeMetrics,
+ updateChannelMetrics: updateChannelMetrics,
+ accountManagerMetrics: accountManagerMetrics,
}, nil
}
From bcce1bf18474e68744d81a6b05b8a32e901ae771 Mon Sep 17 00:00:00 2001
From: Bethuel Mmbaga
Date: Wed, 7 Aug 2024 15:40:43 +0300
Subject: [PATCH 65/77] Update dependencies and switch systray library (#2309)
* Update dependencies and switch systray library
This commit updates the project's dependencies and switches from the 'getlantern/systray' library to the 'fyne.io/systray' library. It also removes some unused dependencies, improving the maintainability and performance of the project. This change in the system tray library is an upgrade which offers more extensive features and better support.
* Remove legacy_appindicator tag from .goreleaser_ui.yaml
---
.goreleaser_ui.yaml | 2 --
client/ui/client_ui.go | 2 +-
go.mod | 10 +---------
go.sum | 21 ++-------------------
4 files changed, 4 insertions(+), 31 deletions(-)
diff --git a/.goreleaser_ui.yaml b/.goreleaser_ui.yaml
index b13085e86ff..fd92b5328d8 100644
--- a/.goreleaser_ui.yaml
+++ b/.goreleaser_ui.yaml
@@ -11,8 +11,6 @@ builds:
- amd64
ldflags:
- -s -w -X github.com/netbirdio/netbird/version.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
- tags:
- - legacy_appindicator
mod_timestamp: '{{ .CommitTimestamp }}'
- id: netbird-ui-windows
diff --git a/client/ui/client_ui.go b/client/ui/client_ui.go
index 58004dd4a5d..d046bab5f1f 100644
--- a/client/ui/client_ui.go
+++ b/client/ui/client_ui.go
@@ -22,8 +22,8 @@ import (
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
+ "fyne.io/systray"
"github.com/cenkalti/backoff/v4"
- "github.com/getlantern/systray"
log "github.com/sirupsen/logrus"
"github.com/skratchdot/open-golang/open"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
diff --git a/go.mod b/go.mod
index 7f242c2033a..25e726769ef 100644
--- a/go.mod
+++ b/go.mod
@@ -31,6 +31,7 @@ require (
require (
fyne.io/fyne/v2 v2.1.4
+ fyne.io/systray v1.11.0
github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible
github.com/c-robinson/iplib v1.0.3
github.com/cilium/ebpf v0.15.0
@@ -38,7 +39,6 @@ require (
github.com/creack/pty v1.1.18
github.com/eko/gocache/v3 v3.1.1
github.com/fsnotify/fsnotify v1.6.0
- github.com/getlantern/systray v1.2.1
github.com/gliderlabs/ssh v0.3.4
github.com/godbus/dbus/v5 v5.1.0
github.com/golang/mock v1.6.0
@@ -120,19 +120,12 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect
- github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
- github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
- github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect
- github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
- github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
- github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
- github.com/go-stack/stack v1.8.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
@@ -164,7 +157,6 @@ require (
github.com/nxadm/tail v1.4.8 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
- github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/pegasus-kv/thrift v0.13.0 // indirect
github.com/pion/dtls/v2 v2.2.10 // indirect
github.com/pion/mdns v0.0.12 // indirect
diff --git a/go.sum b/go.sum
index 1251a6fd7ca..2e2cadd43a5 100644
--- a/go.sum
+++ b/go.sum
@@ -12,6 +12,8 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
fyne.io/fyne/v2 v2.1.4 h1:bt1+28++kAzRzPB0GM2EuSV4cnl8rXNX4cjfd8G06Rc=
fyne.io/fyne/v2 v2.1.4/go.mod h1:p+E/Dh+wPW8JwR2DVcsZ9iXgR9ZKde80+Y+40Is54AQ=
+fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
+fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
@@ -111,18 +113,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
-github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
-github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
-github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
-github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
-github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
-github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
-github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
-github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
-github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
-github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
-github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
-github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
@@ -151,8 +141,6 @@ 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-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
-github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
@@ -337,8 +325,6 @@ github.com/netbirdio/management-integrations/integrations v0.0.0-20240703085513-
github.com/netbirdio/management-integrations/integrations v0.0.0-20240703085513-32605f7ffd8e/go.mod h1:nykwWZnxb+sJz2Z//CEq45CMRWSHllH8pODKRB8eY7Y=
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g=
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
-github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM=
-github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
github.com/netbirdio/wireguard-go v0.0.0-20240105182236-6c340dd55aed h1:t0UADZUJDaaZgfKrt8JUPrOLL9Mg/ryjP85RAH53qgs=
github.com/netbirdio/wireguard-go v0.0.0-20240105182236-6c340dd55aed/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
@@ -368,8 +354,6 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
-github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
-github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY8d4=
@@ -609,7 +593,6 @@ golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
From 0911163146d2894680394435c48babb1a1529421 Mon Sep 17 00:00:00 2001
From: Bethuel Mmbaga
Date: Thu, 8 Aug 2024 18:01:38 +0300
Subject: [PATCH 66/77] Add batch delete for groups and users (#2370)
* Refactor user deletion logic and introduce batch delete
* Prevent self-deletion for users
* Add delete multiple groups
* Refactor group deletion with validation
* Fix tests
* Add bulk delete functions for Users and Groups in account manager interface and mocks
* Add tests for DeleteGroups method in group management
* Add tests for DeleteUsers method in users management
---
management/server/account.go | 2 +
management/server/group.go | 217 ++++++++++++------
management/server/group_test.go | 145 +++++++++++-
management/server/mock_server/account_mock.go | 18 ++
management/server/user.go | 146 +++++++++---
management/server/user_test.go | 151 ++++++++++++
6 files changed, 564 insertions(+), 115 deletions(-)
diff --git a/management/server/account.go b/management/server/account.go
index e99e0e7f361..ca53ebad04b 100644
--- a/management/server/account.go
+++ b/management/server/account.go
@@ -68,6 +68,7 @@ type AccountManager interface {
SaveSetupKey(ctx context.Context, accountID string, key *SetupKey, userID string) (*SetupKey, error)
CreateUser(ctx context.Context, accountID, initiatorUserID string, key *UserInfo) (*UserInfo, error)
DeleteUser(ctx context.Context, accountID, initiatorUserID string, targetUserID string) error
+ DeleteRegularUsers(ctx context.Context, accountID, initiatorUserID string, targetUserIDs []string) error
InviteUser(ctx context.Context, accountID string, initiatorUserID string, targetUserID string) error
ListSetupKeys(ctx context.Context, accountID, userID string) ([]*SetupKey, error)
SaveUser(ctx context.Context, accountID, initiatorUserID string, update *User) (*UserInfo, error)
@@ -101,6 +102,7 @@ type AccountManager interface {
SaveGroup(ctx context.Context, accountID, userID string, group *nbgroup.Group) error
SaveGroups(ctx context.Context, accountID, userID string, newGroups []*nbgroup.Group) error
DeleteGroup(ctx context.Context, accountId, userId, groupID string) error
+ DeleteGroups(ctx context.Context, accountId, userId string, groupIDs []string) error
ListGroups(ctx context.Context, accountId string) ([]*nbgroup.Group, error)
GroupAddPeer(ctx context.Context, accountId, groupID, peerID string) error
GroupDeletePeer(ctx context.Context, accountId, groupID, peerID string) error
diff --git a/management/server/group.go b/management/server/group.go
index 37a6fc305fb..49720f34730 100644
--- a/management/server/group.go
+++ b/management/server/group.go
@@ -2,8 +2,12 @@ package server
import (
"context"
+ "errors"
"fmt"
+ "slices"
+ nbdns "github.com/netbirdio/netbird/dns"
+ "github.com/netbirdio/netbird/route"
"github.com/rs/xid"
log "github.com/sirupsen/logrus"
@@ -243,7 +247,7 @@ func difference(a, b []string) []string {
return diff
}
-// DeleteGroup object of the peers
+// DeleteGroup object of the peers.
func (am *DefaultAccountManager) DeleteGroup(ctx context.Context, accountId, userId, groupID string) error {
unlock := am.Store.AcquireWriteLockByUID(ctx, accountId)
defer unlock()
@@ -253,108 +257,70 @@ func (am *DefaultAccountManager) DeleteGroup(ctx context.Context, accountId, use
return err
}
- g, ok := account.Groups[groupID]
+ group, ok := account.Groups[groupID]
if !ok {
return nil
}
- // disable a deleting integration group if the initiator is not an admin service user
- if g.Issued == nbgroup.GroupIssuedIntegration {
- executingUser := account.Users[userId]
- if executingUser == nil {
- return status.Errorf(status.NotFound, "user not found")
- }
- if executingUser.Role != UserRoleAdmin || !executingUser.IsServiceUser {
- return status.Errorf(status.PermissionDenied, "only service users with admin power can delete integration group")
- }
+ if err = validateDeleteGroup(account, group, userId); err != nil {
+ return err
}
+ delete(account.Groups, groupID)
- // check route links
- for _, r := range account.Routes {
- for _, g := range r.Groups {
- if g == groupID {
- return &GroupLinkError{"route", string(r.NetID)}
- }
- }
- for _, g := range r.PeerGroups {
- if g == groupID {
- return &GroupLinkError{"route", string(r.NetID)}
- }
- }
+ account.Network.IncSerial()
+ if err = am.Store.SaveAccount(ctx, account); err != nil {
+ return err
}
- // check DNS links
- for _, dns := range account.NameServerGroups {
- for _, g := range dns.Groups {
- if g == groupID {
- return &GroupLinkError{"name server groups", dns.Name}
- }
- }
- }
+ am.StoreEvent(ctx, userId, groupID, accountId, activity.GroupDeleted, group.EventMeta())
- // check ACL links
- for _, policy := range account.Policies {
- for _, rule := range policy.Rules {
- for _, src := range rule.Sources {
- if src == groupID {
- return &GroupLinkError{"policy", policy.Name}
- }
- }
+ am.updateAccountPeers(ctx, account)
- for _, dst := range rule.Destinations {
- if dst == groupID {
- return &GroupLinkError{"policy", policy.Name}
- }
- }
- }
- }
+ return nil
+}
- // check setup key links
- for _, setupKey := range account.SetupKeys {
- for _, grp := range setupKey.AutoGroups {
- if grp == groupID {
- return &GroupLinkError{"setup key", setupKey.Name}
- }
- }
+// DeleteGroups deletes groups from an account.
+// Note: This function does not acquire the global lock.
+// It is the caller's responsibility to ensure proper locking is in place before invoking this method.
+//
+// If an error occurs while deleting a group, the function skips it and continues deleting other groups.
+// Errors are collected and returned at the end.
+func (am *DefaultAccountManager) DeleteGroups(ctx context.Context, accountId, userId string, groupIDs []string) error {
+ account, err := am.Store.GetAccount(ctx, accountId)
+ if err != nil {
+ return err
}
- // check user links
- for _, user := range account.Users {
- for _, grp := range user.AutoGroups {
- if grp == groupID {
- return &GroupLinkError{"user", user.Id}
- }
- }
- }
+ var allErrors error
- // check DisabledManagementGroups
- for _, disabledMgmGrp := range account.DNSSettings.DisabledManagementGroups {
- if disabledMgmGrp == groupID {
- return &GroupLinkError{"disabled DNS management groups", g.Name}
+ deletedGroups := make([]*nbgroup.Group, 0, len(groupIDs))
+ for _, groupID := range groupIDs {
+ group, ok := account.Groups[groupID]
+ if !ok {
+ continue
}
- }
- // check integrated peer validator groups
- if account.Settings.Extra != nil {
- for _, integratedPeerValidatorGroups := range account.Settings.Extra.IntegratedValidatorGroups {
- if groupID == integratedPeerValidatorGroups {
- return &GroupLinkError{"integrated validator", g.Name}
- }
+ if err := validateDeleteGroup(account, group, userId); err != nil {
+ allErrors = errors.Join(allErrors, fmt.Errorf("failed to delete group %s: %w", groupID, err))
+ continue
}
- }
- delete(account.Groups, groupID)
+ delete(account.Groups, groupID)
+ deletedGroups = append(deletedGroups, group)
+ }
account.Network.IncSerial()
if err = am.Store.SaveAccount(ctx, account); err != nil {
return err
}
- am.StoreEvent(ctx, userId, groupID, accountId, activity.GroupDeleted, g.EventMeta())
+ for _, g := range deletedGroups {
+ am.StoreEvent(ctx, userId, g.ID, accountId, activity.GroupDeleted, g.EventMeta())
+ }
am.updateAccountPeers(ctx, account)
- return nil
+ return allErrors
}
// ListGroups objects of the peers
@@ -440,3 +406,102 @@ func (am *DefaultAccountManager) GroupDeletePeer(ctx context.Context, accountID,
return nil
}
+
+func validateDeleteGroup(account *Account, group *nbgroup.Group, userID string) error {
+ // disable a deleting integration group if the initiator is not an admin service user
+ if group.Issued == nbgroup.GroupIssuedIntegration {
+ executingUser := account.Users[userID]
+ if executingUser == nil {
+ return status.Errorf(status.NotFound, "user not found")
+ }
+ if executingUser.Role != UserRoleAdmin || !executingUser.IsServiceUser {
+ return status.Errorf(status.PermissionDenied, "only service users with admin power can delete integration group")
+ }
+ }
+
+ if isLinked, linkedRoute := isGroupLinkedToRoute(account.Routes, group.ID); isLinked {
+ return &GroupLinkError{"route", string(linkedRoute.NetID)}
+ }
+
+ if isLinked, linkedDns := isGroupLinkedToDns(account.NameServerGroups, group.ID); isLinked {
+ return &GroupLinkError{"name server groups", linkedDns.Name}
+ }
+
+ if isLinked, linkedPolicy := isGroupLinkedToPolicy(account.Policies, group.ID); isLinked {
+ return &GroupLinkError{"policy", linkedPolicy.Name}
+ }
+
+ if isLinked, linkedSetupKey := isGroupLinkedToSetupKey(account.SetupKeys, group.ID); isLinked {
+ return &GroupLinkError{"setup key", linkedSetupKey.Name}
+ }
+
+ if isLinked, linkedUser := isGroupLinkedToUser(account.Users, group.ID); isLinked {
+ return &GroupLinkError{"user", linkedUser.Id}
+ }
+
+ if slices.Contains(account.DNSSettings.DisabledManagementGroups, group.ID) {
+ return &GroupLinkError{"disabled DNS management groups", group.Name}
+ }
+
+ if account.Settings.Extra != nil {
+ if slices.Contains(account.Settings.Extra.IntegratedValidatorGroups, group.ID) {
+ return &GroupLinkError{"integrated validator", group.Name}
+ }
+ }
+
+ return nil
+}
+
+// isGroupLinkedToRoute checks if a group is linked to any route in the account.
+func isGroupLinkedToRoute(routes map[route.ID]*route.Route, groupID string) (bool, *route.Route) {
+ for _, r := range routes {
+ if slices.Contains(r.Groups, groupID) || slices.Contains(r.PeerGroups, groupID) {
+ return true, r
+ }
+ }
+ return false, nil
+}
+
+// isGroupLinkedToPolicy checks if a group is linked to any policy in the account.
+func isGroupLinkedToPolicy(policies []*Policy, groupID string) (bool, *Policy) {
+ for _, policy := range policies {
+ for _, rule := range policy.Rules {
+ if slices.Contains(rule.Sources, groupID) || slices.Contains(rule.Destinations, groupID) {
+ return true, policy
+ }
+ }
+ }
+ return false, nil
+}
+
+// isGroupLinkedToDns checks if a group is linked to any nameserver group in the account.
+func isGroupLinkedToDns(nameServerGroups map[string]*nbdns.NameServerGroup, groupID string) (bool, *nbdns.NameServerGroup) {
+ for _, dns := range nameServerGroups {
+ for _, g := range dns.Groups {
+ if g == groupID {
+ return true, dns
+ }
+ }
+ }
+ return false, nil
+}
+
+// isGroupLinkedToSetupKey checks if a group is linked to any setup key in the account.
+func isGroupLinkedToSetupKey(setupKeys map[string]*SetupKey, groupID string) (bool, *SetupKey) {
+ for _, setupKey := range setupKeys {
+ if slices.Contains(setupKey.AutoGroups, groupID) {
+ return true, setupKey
+ }
+ }
+ return false, nil
+}
+
+// isGroupLinkedToUser checks if a group is linked to any user in the account.
+func isGroupLinkedToUser(users map[string]*User, groupID string) (bool, *User) {
+ for _, user := range users {
+ if slices.Contains(user.AutoGroups, groupID) {
+ return true, user
+ }
+ }
+ return false, nil
+}
diff --git a/management/server/group_test.go b/management/server/group_test.go
index 373d7296433..89b68ad6c07 100644
--- a/management/server/group_test.go
+++ b/management/server/group_test.go
@@ -3,12 +3,14 @@ package server
import (
"context"
"errors"
+ "fmt"
"testing"
nbdns "github.com/netbirdio/netbird/dns"
nbgroup "github.com/netbirdio/netbird/management/server/group"
"github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/route"
+ "github.com/stretchr/testify/assert"
)
const (
@@ -21,7 +23,7 @@ func TestDefaultAccountManager_CreateGroup(t *testing.T) {
t.Error("failed to create account manager")
}
- account, err := initTestGroupAccount(am)
+ _, account, err := initTestGroupAccount(am)
if err != nil {
t.Error("failed to init testing account")
}
@@ -56,7 +58,7 @@ func TestDefaultAccountManager_DeleteGroup(t *testing.T) {
t.Error("failed to create account manager")
}
- account, err := initTestGroupAccount(am)
+ _, account, err := initTestGroupAccount(am)
if err != nil {
t.Error("failed to init testing account")
}
@@ -132,7 +134,136 @@ func TestDefaultAccountManager_DeleteGroup(t *testing.T) {
}
}
-func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
+func TestDefaultAccountManager_DeleteGroups(t *testing.T) {
+ am, err := createManager(t)
+ assert.NoError(t, err, "Failed to create account manager")
+
+ manager, account, err := initTestGroupAccount(am)
+ assert.NoError(t, err, "Failed to init testing account")
+
+ groups := make([]*nbgroup.Group, 10)
+ for i := 0; i < 10; i++ {
+ groups[i] = &nbgroup.Group{
+ ID: fmt.Sprintf("group-%d", i+1),
+ AccountID: account.Id,
+ Name: fmt.Sprintf("group-%d", i+1),
+ Issued: nbgroup.GroupIssuedAPI,
+ }
+ }
+
+ err = manager.SaveGroups(context.Background(), account.Id, groupAdminUserID, groups)
+ assert.NoError(t, err, "Failed to save test groups")
+
+ testCases := []struct {
+ name string
+ groupIDs []string
+ expectedReasons []string
+ expectedDeleted []string
+ expectedNotDeleted []string
+ }{
+ {
+ name: "route",
+ groupIDs: []string{"grp-for-route"},
+ expectedReasons: []string{"route"},
+ },
+ {
+ name: "route with peer groups",
+ groupIDs: []string{"grp-for-route2"},
+ expectedReasons: []string{"route"},
+ },
+ {
+ name: "name server groups",
+ groupIDs: []string{"grp-for-name-server-grp"},
+ expectedReasons: []string{"name server groups"},
+ },
+ {
+ name: "policy",
+ groupIDs: []string{"grp-for-policies"},
+ expectedReasons: []string{"policy"},
+ },
+ {
+ name: "setup keys",
+ groupIDs: []string{"grp-for-keys"},
+ expectedReasons: []string{"setup key"},
+ },
+ {
+ name: "users",
+ groupIDs: []string{"grp-for-users"},
+ expectedReasons: []string{"user"},
+ },
+ {
+ name: "integration",
+ groupIDs: []string{"grp-for-integration"},
+ expectedReasons: []string{"only service users with admin power can delete integration group"},
+ },
+ {
+ name: "successfully delete multiple groups",
+ groupIDs: []string{"group-1", "group-2"},
+ expectedDeleted: []string{"group-1", "group-2"},
+ },
+ {
+ name: "delete non-existent group",
+ groupIDs: []string{"non-existent-group"},
+ expectedDeleted: []string{"non-existent-group"},
+ },
+ {
+ name: "delete multiple groups with mixed results",
+ groupIDs: []string{"group-3", "grp-for-policies", "group-4", "grp-for-users"},
+ expectedReasons: []string{"policy", "user"},
+ expectedDeleted: []string{"group-3", "group-4"},
+ expectedNotDeleted: []string{"grp-for-policies", "grp-for-users"},
+ },
+ {
+ name: "delete groups with multiple errors",
+ groupIDs: []string{"grp-for-policies", "grp-for-users"},
+ expectedReasons: []string{"policy", "user"},
+ expectedNotDeleted: []string{"grp-for-policies", "grp-for-users"},
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ err = am.DeleteGroups(context.Background(), account.Id, groupAdminUserID, tc.groupIDs)
+ if len(tc.expectedReasons) > 0 {
+ assert.Error(t, err)
+ var foundExpectedErrors int
+
+ wrappedErr, ok := err.(interface{ Unwrap() []error })
+ assert.Equal(t, ok, true)
+
+ for _, e := range wrappedErr.Unwrap() {
+ var sErr *status.Error
+ if errors.As(e, &sErr) {
+ assert.Contains(t, tc.expectedReasons, sErr.Message, "unexpected error message")
+ foundExpectedErrors++
+ }
+
+ var gErr *GroupLinkError
+ if errors.As(e, &gErr) {
+ assert.Contains(t, tc.expectedReasons, gErr.Resource, "unexpected error resource")
+ foundExpectedErrors++
+ }
+ }
+ assert.Equal(t, len(tc.expectedReasons), foundExpectedErrors, "not all expected errors were found")
+ } else {
+ assert.NoError(t, err)
+ }
+
+ for _, groupID := range tc.expectedDeleted {
+ _, err := am.GetGroup(context.Background(), account.Id, groupID, groupAdminUserID)
+ assert.Error(t, err, "group should have been deleted: %s", groupID)
+ }
+
+ for _, groupID := range tc.expectedNotDeleted {
+ group, err := am.GetGroup(context.Background(), account.Id, groupID, groupAdminUserID)
+ assert.NoError(t, err, "group should not have been deleted: %s", groupID)
+ assert.NotNil(t, group, "group should exist: %s", groupID)
+ }
+ })
+ }
+}
+
+func initTestGroupAccount(am *DefaultAccountManager) (*DefaultAccountManager, *Account, error) {
accountID := "testingAcc"
domain := "example.com"
@@ -236,7 +367,7 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
err := am.Store.SaveAccount(context.Background(), account)
if err != nil {
- return nil, err
+ return nil, nil, err
}
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForRoute)
@@ -247,5 +378,9 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForUsers)
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForIntegration)
- return am.Store.GetAccount(context.Background(), account.Id)
+ acc, err := am.Store.GetAccount(context.Background(), account.Id)
+ if err != nil {
+ return nil, nil, err
+ }
+ return am, acc, nil
}
diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go
index a66bdee2b90..49532525279 100644
--- a/management/server/mock_server/account_mock.go
+++ b/management/server/mock_server/account_mock.go
@@ -42,6 +42,7 @@ type MockAccountManager struct {
SaveGroupFunc func(ctx context.Context, accountID, userID string, group *group.Group) error
SaveGroupsFunc func(ctx context.Context, accountID, userID string, groups []*group.Group) error
DeleteGroupFunc func(ctx context.Context, accountID, userId, groupID string) error
+ DeleteGroupsFunc func(ctx context.Context, accountId, userId string, groupIDs []string) error
ListGroupsFunc func(ctx context.Context, accountID string) ([]*group.Group, error)
GroupAddPeerFunc func(ctx context.Context, accountID, groupID, peerID string) error
GroupDeletePeerFunc func(ctx context.Context, accountID, groupID, peerID string) error
@@ -67,6 +68,7 @@ type MockAccountManager struct {
SaveOrAddUserFunc func(ctx context.Context, accountID, userID string, user *server.User, addIfNotExists bool) (*server.UserInfo, error)
SaveOrAddUsersFunc func(ctx context.Context, accountID, initiatorUserID string, update []*server.User, addIfNotExists bool) ([]*server.UserInfo, error)
DeleteUserFunc func(ctx context.Context, accountID string, initiatorUserID string, targetUserID string) error
+ DeleteRegularUsersFunc func(ctx context.Context, accountID, initiatorUserID string, targetUserIDs []string) error
CreatePATFunc func(ctx context.Context, accountID string, initiatorUserID string, targetUserId string, tokenName string, expiresIn int) (*server.PersonalAccessTokenGenerated, error)
DeletePATFunc func(ctx context.Context, accountID string, initiatorUserID string, targetUserId string, tokenID string) error
GetPATFunc func(ctx context.Context, accountID string, initiatorUserID string, targetUserId string, tokenID string) (*server.PersonalAccessToken, error)
@@ -326,6 +328,14 @@ func (am *MockAccountManager) DeleteGroup(ctx context.Context, accountId, userId
return status.Errorf(codes.Unimplemented, "method DeleteGroup is not implemented")
}
+// DeleteGroups mock implementation of DeleteGroups from server.AccountManager interface
+func (am *MockAccountManager) DeleteGroups(ctx context.Context, accountId, userId string, groupIDs []string) error {
+ if am.DeleteGroupsFunc != nil {
+ return am.DeleteGroupsFunc(ctx, accountId, userId, groupIDs)
+ }
+ return status.Errorf(codes.Unimplemented, "method DeleteGroups is not implemented")
+}
+
// ListGroups mock implementation of ListGroups from server.AccountManager interface
func (am *MockAccountManager) ListGroups(ctx context.Context, accountID string) ([]*group.Group, error) {
if am.ListGroupsFunc != nil {
@@ -528,6 +538,14 @@ func (am *MockAccountManager) DeleteUser(ctx context.Context, accountID string,
return status.Errorf(codes.Unimplemented, "method DeleteUser is not implemented")
}
+// DeleteRegularUsers mocks DeleteRegularUsers of the AccountManager interface
+func (am *MockAccountManager) DeleteRegularUsers(ctx context.Context, accountID string, initiatorUserID string, targetUserIDs []string) error {
+ if am.DeleteRegularUsersFunc != nil {
+ return am.DeleteRegularUsersFunc(ctx, accountID, initiatorUserID, targetUserIDs)
+ }
+ return status.Errorf(codes.Unimplemented, "method DeleteRegularUsers is not implemented")
+}
+
func (am *MockAccountManager) InviteUser(ctx context.Context, accountID string, initiatorUserID string, targetUserID string) error {
if am.InviteUserFunc != nil {
return am.InviteUserFunc(ctx, accountID, initiatorUserID, targetUserID)
diff --git a/management/server/user.go b/management/server/user.go
index b8afcda3ac7..8fd0a16273e 100644
--- a/management/server/user.go
+++ b/management/server/user.go
@@ -2,6 +2,7 @@ package server
import (
"context"
+ "errors"
"fmt"
"strings"
"time"
@@ -472,51 +473,18 @@ func (am *DefaultAccountManager) DeleteUser(ctx context.Context, accountID, init
}
func (am *DefaultAccountManager) deleteRegularUser(ctx context.Context, account *Account, initiatorUserID, targetUserID string) error {
- tuEmail, tuName, err := am.getEmailAndNameOfTargetUser(ctx, account.Id, initiatorUserID, targetUserID)
- if err != nil {
- log.WithContext(ctx).Errorf("failed to resolve email address: %s", err)
- return err
- }
-
- if !isNil(am.idpManager) {
- // Delete if the user already exists in the IdP.Necessary in cases where a user account
- // was created where a user account was provisioned but the user did not sign in
- _, err = am.idpManager.GetUserDataByID(ctx, targetUserID, idp.AppMetadata{WTAccountID: account.Id})
- if err == nil {
- err = am.deleteUserFromIDP(ctx, targetUserID, account.Id)
- if err != nil {
- log.WithContext(ctx).Debugf("failed to delete user from IDP: %s", targetUserID)
- return err
- }
- } else {
- log.WithContext(ctx).Debugf("skipped deleting user %s from IDP, error: %v", targetUserID, err)
- }
- }
-
- err = am.deleteUserPeers(ctx, initiatorUserID, targetUserID, account)
+ meta, err := am.prepareUserDeletion(ctx, account, initiatorUserID, targetUserID)
if err != nil {
return err
}
- u, err := account.FindUser(targetUserID)
- if err != nil {
- log.WithContext(ctx).Errorf("failed to find user %s for deletion, this should never happen: %s", targetUserID, err)
- }
-
- var tuCreatedAt time.Time
- if u != nil {
- tuCreatedAt = u.CreatedAt
- }
-
delete(account.Users, targetUserID)
err = am.Store.SaveAccount(ctx, account)
if err != nil {
return err
}
- meta := map[string]any{"name": tuName, "email": tuEmail, "created_at": tuCreatedAt}
am.StoreEvent(ctx, initiatorUserID, targetUserID, account.Id, activity.UserDeleted, meta)
-
am.updateAccountPeers(ctx, account)
return nil
@@ -1190,6 +1158,116 @@ func (am *DefaultAccountManager) getEmailAndNameOfTargetUser(ctx context.Context
return "", "", fmt.Errorf("user info not found for user: %s", targetId)
}
+// DeleteRegularUsers deletes regular users from an account.
+// Note: This function does not acquire the global lock.
+// It is the caller's responsibility to ensure proper locking is in place before invoking this method.
+//
+// If an error occurs while deleting the user, the function skips it and continues deleting other users.
+// Errors are collected and returned at the end.
+func (am *DefaultAccountManager) DeleteRegularUsers(ctx context.Context, accountID, initiatorUserID string, targetUserIDs []string) error {
+ account, err := am.Store.GetAccount(ctx, accountID)
+ if err != nil {
+ return err
+ }
+
+ executingUser := account.Users[initiatorUserID]
+ if executingUser == nil {
+ return status.Errorf(status.NotFound, "user not found")
+ }
+ if !executingUser.HasAdminPower() {
+ return status.Errorf(status.PermissionDenied, "only users with admin power can delete users")
+ }
+
+ var allErrors error
+
+ deletedUsersMeta := make(map[string]map[string]any)
+ for _, targetUserID := range targetUserIDs {
+ if initiatorUserID == targetUserID {
+ allErrors = errors.Join(allErrors, errors.New("self deletion is not allowed"))
+ continue
+ }
+
+ targetUser := account.Users[targetUserID]
+ if targetUser == nil {
+ allErrors = errors.Join(allErrors, fmt.Errorf("target user: %s not found", targetUserID))
+ continue
+ }
+
+ if targetUser.Role == UserRoleOwner {
+ allErrors = errors.Join(allErrors, fmt.Errorf("unable to delete a user: %s with owner role", targetUserID))
+ continue
+ }
+
+ // disable deleting integration user if the initiator is not admin service user
+ if targetUser.Issued == UserIssuedIntegration && !executingUser.IsServiceUser {
+ allErrors = errors.Join(allErrors, errors.New("only integration service user can delete this user"))
+ continue
+ }
+
+ meta, err := am.prepareUserDeletion(ctx, account, initiatorUserID, targetUserID)
+ if err != nil {
+ allErrors = errors.Join(allErrors, fmt.Errorf("failed to delete user %s: %s", targetUserID, err))
+ continue
+ }
+
+ delete(account.Users, targetUserID)
+ deletedUsersMeta[targetUserID] = meta
+ }
+
+ err = am.Store.SaveAccount(ctx, account)
+ if err != nil {
+ return fmt.Errorf("failed to delete users: %w", err)
+ }
+
+ am.updateAccountPeers(ctx, account)
+
+ for targetUserID, meta := range deletedUsersMeta {
+ am.StoreEvent(ctx, initiatorUserID, targetUserID, account.Id, activity.UserDeleted, meta)
+ }
+
+ return allErrors
+}
+
+func (am *DefaultAccountManager) prepareUserDeletion(ctx context.Context, account *Account, initiatorUserID, targetUserID string) (map[string]any, error) {
+ tuEmail, tuName, err := am.getEmailAndNameOfTargetUser(ctx, account.Id, initiatorUserID, targetUserID)
+ if err != nil {
+ log.WithContext(ctx).Errorf("failed to resolve email address: %s", err)
+ return nil, err
+ }
+
+ if !isNil(am.idpManager) {
+ // Delete if the user already exists in the IdP. Necessary in cases where a user account
+ // was created where a user account was provisioned but the user did not sign in
+ _, err = am.idpManager.GetUserDataByID(ctx, targetUserID, idp.AppMetadata{WTAccountID: account.Id})
+ if err == nil {
+ err = am.deleteUserFromIDP(ctx, targetUserID, account.Id)
+ if err != nil {
+ log.WithContext(ctx).Debugf("failed to delete user from IDP: %s", targetUserID)
+ return nil, err
+ }
+ } else {
+ log.WithContext(ctx).Debugf("skipped deleting user %s from IDP, error: %v", targetUserID, err)
+ }
+ }
+
+ err = am.deleteUserPeers(ctx, initiatorUserID, targetUserID, account)
+ if err != nil {
+ return nil, err
+ }
+
+ u, err := account.FindUser(targetUserID)
+ if err != nil {
+ log.WithContext(ctx).Errorf("failed to find user %s for deletion, this should never happen: %s", targetUserID, err)
+ }
+
+ var tuCreatedAt time.Time
+ if u != nil {
+ tuCreatedAt = u.CreatedAt
+ }
+
+ return map[string]any{"name": tuName, "email": tuEmail, "created_at": tuCreatedAt}, nil
+}
+
func findUserInIDPUserdata(userID string, userData []*idp.UserData) (*idp.UserData, bool) {
for _, user := range userData {
if user.ID == userID {
diff --git a/management/server/user_test.go b/management/server/user_test.go
index 99d2792dfa6..2720602765a 100644
--- a/management/server/user_test.go
+++ b/management/server/user_test.go
@@ -662,6 +662,157 @@ func TestUser_DeleteUser_regularUser(t *testing.T) {
}
+func TestUser_DeleteUser_RegularUsers(t *testing.T) {
+ store := newStore(t)
+ defer store.Close(context.Background())
+ account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+
+ targetId := "user2"
+ account.Users[targetId] = &User{
+ Id: targetId,
+ IsServiceUser: true,
+ ServiceUserName: "user2username",
+ }
+ targetId = "user3"
+ account.Users[targetId] = &User{
+ Id: targetId,
+ IsServiceUser: false,
+ Issued: UserIssuedAPI,
+ }
+ targetId = "user4"
+ account.Users[targetId] = &User{
+ Id: targetId,
+ IsServiceUser: false,
+ Issued: UserIssuedIntegration,
+ }
+
+ targetId = "user5"
+ account.Users[targetId] = &User{
+ Id: targetId,
+ IsServiceUser: false,
+ Issued: UserIssuedAPI,
+ Role: UserRoleOwner,
+ }
+ account.Users["user6"] = &User{
+ Id: "user6",
+ IsServiceUser: false,
+ Issued: UserIssuedAPI,
+ }
+ account.Users["user7"] = &User{
+ Id: "user7",
+ IsServiceUser: false,
+ Issued: UserIssuedAPI,
+ }
+ account.Users["user8"] = &User{
+ Id: "user8",
+ IsServiceUser: false,
+ Issued: UserIssuedAPI,
+ Role: UserRoleAdmin,
+ }
+ account.Users["user9"] = &User{
+ Id: "user9",
+ IsServiceUser: false,
+ Issued: UserIssuedAPI,
+ Role: UserRoleAdmin,
+ }
+
+ err := store.SaveAccount(context.Background(), account)
+ if err != nil {
+ t.Fatalf("Error when saving account: %s", err)
+ }
+
+ am := DefaultAccountManager{
+ Store: store,
+ eventStore: &activity.InMemoryEventStore{},
+ integratedPeerValidator: MocIntegratedValidator{},
+ }
+
+ testCases := []struct {
+ name string
+ userIDs []string
+ expectedReasons []string
+ expectedDeleted []string
+ expectedNotDeleted []string
+ }{
+ {
+ name: "Delete service user successfully ",
+ userIDs: []string{"user2"},
+ expectedDeleted: []string{"user2"},
+ },
+ {
+ name: "Delete regular user successfully",
+ userIDs: []string{"user3"},
+ expectedDeleted: []string{"user3"},
+ },
+ {
+ name: "Delete integration regular user permission denied",
+ userIDs: []string{"user4"},
+ expectedReasons: []string{"only integration service user can delete this user"},
+ expectedNotDeleted: []string{"user4"},
+ },
+ {
+ name: "Delete user with owner role should return permission denied",
+ userIDs: []string{"user5"},
+ expectedReasons: []string{"unable to delete a user: user5 with owner role"},
+ expectedNotDeleted: []string{"user5"},
+ },
+ {
+ name: "Delete multiple users with mixed results",
+ userIDs: []string{"user5", "user5", "user6", "user7"},
+ expectedReasons: []string{"only integration service user can delete this user", "unable to delete a user: user5 with owner role"},
+ expectedDeleted: []string{"user6", "user7"},
+ expectedNotDeleted: []string{"user4", "user5"},
+ },
+ {
+ name: "Delete non-existent user",
+ userIDs: []string{"non-existent-user"},
+ expectedReasons: []string{"target user: non-existent-user not found"},
+ expectedNotDeleted: []string{},
+ },
+ {
+ name: "Delete multiple regular users successfully",
+ userIDs: []string{"user8", "user9"},
+ expectedDeleted: []string{"user8", "user9"},
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ err = am.DeleteRegularUsers(context.Background(), mockAccountID, mockUserID, tc.userIDs)
+ if len(tc.expectedReasons) > 0 {
+ assert.Error(t, err)
+ var foundExpectedErrors int
+
+ wrappedErr, ok := err.(interface{ Unwrap() []error })
+ assert.Equal(t, ok, true)
+
+ for _, e := range wrappedErr.Unwrap() {
+ assert.Contains(t, tc.expectedReasons, e.Error(), "unexpected error message")
+ foundExpectedErrors++
+ }
+
+ assert.Equal(t, len(tc.expectedReasons), foundExpectedErrors, "not all expected errors were found")
+ } else {
+ assert.NoError(t, err)
+ }
+
+ acc, err := am.GetAccountByUserOrAccountID(context.Background(), "", account.Id, "")
+ assert.NoError(t, err)
+
+ for _, id := range tc.expectedDeleted {
+ _, exists := acc.Users[id]
+ assert.False(t, exists, "user should have been deleted: %s", id)
+ }
+
+ for _, id := range tc.expectedNotDeleted {
+ user, exists := acc.Users[id]
+ assert.True(t, exists, "user should not have been deleted: %s", id)
+ assert.NotNil(t, user, "user should exist: %s", id)
+ }
+ })
+ }
+}
+
func TestDefaultAccountManager_GetUser(t *testing.T) {
store := newStore(t)
defer store.Close(context.Background())
From 18cef8280ad7dc0eebf38a57aad7e6a3988a8825 Mon Sep 17 00:00:00 2001
From: David Merris
Date: Fri, 9 Aug 2024 11:32:09 -0400
Subject: [PATCH 67/77] [client] Allow setup keys to be provided in a file
(#2337)
Adds a flag and a bit of logic to allow a setup key to be passed in using a file. The flag should be exclusive with the standard --setup-key flag.
---
client/cmd/login.go | 7 +++++++
client/cmd/root.go | 8 ++++++++
client/cmd/up.go | 7 +++++++
client/cmd/up_daemon_test.go | 31 +++++++++++++++++++++++++++++++
4 files changed, 53 insertions(+)
diff --git a/client/cmd/login.go b/client/cmd/login.go
index 14c973d916a..512fbb08133 100644
--- a/client/cmd/login.go
+++ b/client/cmd/login.go
@@ -39,6 +39,13 @@ var loginCmd = &cobra.Command{
ctx = context.WithValue(ctx, system.DeviceNameCtxKey, hostName)
}
+ if setupKeyPath != "" && setupKey == "" {
+ setupKey, err = getSetupKeyFromFile(setupKeyPath)
+ if err != nil {
+ return err
+ }
+ }
+
// workaround to run without service
if logFile == "console" {
err = handleRebrand(cmd)
diff --git a/client/cmd/root.go b/client/cmd/root.go
index db02ff5eaad..b6d6694ee68 100644
--- a/client/cmd/root.go
+++ b/client/cmd/root.go
@@ -56,6 +56,7 @@ var (
managementURL string
adminURL string
setupKey string
+ setupKeyPath string
hostName string
preSharedKey string
natExternalIPs []string
@@ -128,6 +129,8 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", "info", "sets Netbird log level")
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the log will be output to stdout. If syslog is specified the log will be sent to syslog daemon.")
rootCmd.PersistentFlags().StringVarP(&setupKey, "setup-key", "k", "", "Setup key obtained from the Management Service Dashboard (used to register peer)")
+ rootCmd.PersistentFlags().StringVar(&setupKeyPath, "setup-key-file", "", "The path to a setup key obtained from the Management Service Dashboard (used to register peer) This is ignored if the setup-key flag is provided.")
+ rootCmd.MarkFlagsMutuallyExclusive("setup-key", "setup-key-file")
rootCmd.PersistentFlags().StringVar(&preSharedKey, preSharedKeyFlag, "", "Sets Wireguard PreSharedKey property. If set, then only peers that have the same key can communicate.")
rootCmd.PersistentFlags().StringVarP(&hostName, "hostname", "n", "", "Sets a custom hostname for the device")
rootCmd.PersistentFlags().BoolVarP(&anonymizeFlag, "anonymize", "A", false, "anonymize IP addresses and non-netbird.io domains in logs and status output")
@@ -253,6 +256,11 @@ var CLIBackOffSettings = &backoff.ExponentialBackOff{
Clock: backoff.SystemClock,
}
+func getSetupKeyFromFile(setupKeyPath string) (string, error) {
+ data, err := os.ReadFile(setupKeyPath)
+ return string(data), err
+}
+
func handleRebrand(cmd *cobra.Command) error {
var err error
if logFile == defaultLogFile {
diff --git a/client/cmd/up.go b/client/cmd/up.go
index f69e9eb2749..0eaf7bc0d61 100644
--- a/client/cmd/up.go
+++ b/client/cmd/up.go
@@ -73,6 +73,13 @@ func upFunc(cmd *cobra.Command, args []string) error {
ctx = context.WithValue(ctx, system.DeviceNameCtxKey, hostName)
}
+ if setupKeyPath != "" && setupKey == "" {
+ setupKey, err = getSetupKeyFromFile(setupKeyPath)
+ if err != nil {
+ return err
+ }
+ }
+
if foregroundMode {
return runInForegroundMode(ctx, cmd)
}
diff --git a/client/cmd/up_daemon_test.go b/client/cmd/up_daemon_test.go
index 0295d2b2175..daf8d0628c2 100644
--- a/client/cmd/up_daemon_test.go
+++ b/client/cmd/up_daemon_test.go
@@ -2,6 +2,7 @@ package cmd
import (
"context"
+ "os"
"testing"
"time"
@@ -40,6 +41,36 @@ func TestUpDaemon(t *testing.T) {
return
}
+ // Test the setup-key-file flag.
+ tempFile, err := os.CreateTemp("", "setup-key")
+ if err != nil {
+ t.Errorf("could not create temp file, got error %v", err)
+ return
+ }
+ defer os.Remove(tempFile.Name())
+ if _, err := tempFile.Write([]byte("A2C8E62B-38F5-4553-B31E-DD66C696CEBB")); err != nil {
+ t.Errorf("could not write to temp file, got error %v", err)
+ return
+ }
+ if err := tempFile.Close(); err != nil {
+ t.Errorf("unable to close file, got error %v", err)
+ }
+ rootCmd.SetArgs([]string{
+ "login",
+ "--daemon-addr", "tcp://" + cliAddr,
+ "--setup-key-file", tempFile.Name(),
+ "--log-file", "",
+ })
+ if err := rootCmd.Execute(); err != nil {
+ t.Errorf("expected no error while running up command, got %v", err)
+ return
+ }
+ time.Sleep(time.Second * 3)
+ if status, err := state.Status(); err != nil && status != internal.StatusIdle {
+ t.Errorf("wrong status after login: %s, %v", internal.StatusIdle, err)
+ return
+ }
+
rootCmd.SetArgs([]string{
"up",
"--daemon-addr", "tcp://" + cliAddr,
From 12f9d12a11ba4dab74dc6af5a2bab8c647986759 Mon Sep 17 00:00:00 2001
From: Viktor Liu <17948409+lixmal@users.noreply.github.com>
Date: Fri, 9 Aug 2024 19:17:28 +0200
Subject: [PATCH 68/77] [misc] Update bug-issue-report.md to include netbird
debug cmd (#2413)
---
.github/ISSUE_TEMPLATE/bug-issue-report.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/.github/ISSUE_TEMPLATE/bug-issue-report.md b/.github/ISSUE_TEMPLATE/bug-issue-report.md
index 0aa6cd77e8d..8971972a270 100644
--- a/.github/ISSUE_TEMPLATE/bug-issue-report.md
+++ b/.github/ISSUE_TEMPLATE/bug-issue-report.md
@@ -35,6 +35,11 @@ Please specify whether you use NetBird Cloud or self-host NetBird's control plan
If applicable, add the `netbird status -d' command output.
+**Do you face any client issues on desktop?**
+
+Please provide the file created by `netbird debug for 1m -AS`.
+We advise reviewing the anonymized files for any remaining PII.
+
**Screenshots**
If applicable, add screenshots to help explain your problem.
From af1b42e538e74fe01b3f4398f95b4521d8ac3298 Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Fri, 9 Aug 2024 20:38:58 +0200
Subject: [PATCH 69/77] [client] Parse data from setup key (#2411)
refactor functions and variable assignment
---
client/cmd/login.go | 12 +++++-------
client/cmd/root.go | 12 +++++++++++-
client/cmd/up.go | 21 ++++++++++++---------
3 files changed, 28 insertions(+), 17 deletions(-)
diff --git a/client/cmd/login.go b/client/cmd/login.go
index 512fbb08133..c7dd0fda1f3 100644
--- a/client/cmd/login.go
+++ b/client/cmd/login.go
@@ -39,11 +39,9 @@ var loginCmd = &cobra.Command{
ctx = context.WithValue(ctx, system.DeviceNameCtxKey, hostName)
}
- if setupKeyPath != "" && setupKey == "" {
- setupKey, err = getSetupKeyFromFile(setupKeyPath)
- if err != nil {
- return err
- }
+ providedSetupKey, err := getSetupKey()
+ if err != nil {
+ return err
}
// workaround to run without service
@@ -69,7 +67,7 @@ var loginCmd = &cobra.Command{
config, _ = internal.UpdateOldManagementURL(ctx, config, configPath)
- err = foregroundLogin(ctx, cmd, config, setupKey)
+ err = foregroundLogin(ctx, cmd, config, providedSetupKey)
if err != nil {
return fmt.Errorf("foreground login failed: %v", err)
}
@@ -88,7 +86,7 @@ var loginCmd = &cobra.Command{
client := proto.NewDaemonServiceClient(conn)
loginRequest := proto.LoginRequest{
- SetupKey: setupKey,
+ SetupKey: providedSetupKey,
ManagementUrl: managementURL,
IsLinuxDesktopClient: isLinuxRunningDesktop(),
Hostname: hostName,
diff --git a/client/cmd/root.go b/client/cmd/root.go
index b6d6694ee68..8dae6e27374 100644
--- a/client/cmd/root.go
+++ b/client/cmd/root.go
@@ -256,9 +256,19 @@ var CLIBackOffSettings = &backoff.ExponentialBackOff{
Clock: backoff.SystemClock,
}
+func getSetupKey() (string, error) {
+ if setupKeyPath != "" && setupKey == "" {
+ return getSetupKeyFromFile(setupKeyPath)
+ }
+ return setupKey, nil
+}
+
func getSetupKeyFromFile(setupKeyPath string) (string, error) {
data, err := os.ReadFile(setupKeyPath)
- return string(data), err
+ if err != nil {
+ return "", fmt.Errorf("failed to read setup key file: %v", err)
+ }
+ return strings.TrimSpace(string(data)), nil
}
func handleRebrand(cmd *cobra.Command) error {
diff --git a/client/cmd/up.go b/client/cmd/up.go
index 0eaf7bc0d61..2ed6e41d216 100644
--- a/client/cmd/up.go
+++ b/client/cmd/up.go
@@ -73,13 +73,6 @@ func upFunc(cmd *cobra.Command, args []string) error {
ctx = context.WithValue(ctx, system.DeviceNameCtxKey, hostName)
}
- if setupKeyPath != "" && setupKey == "" {
- setupKey, err = getSetupKeyFromFile(setupKeyPath)
- if err != nil {
- return err
- }
- }
-
if foregroundMode {
return runInForegroundMode(ctx, cmd)
}
@@ -154,6 +147,11 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command) error {
ic.DNSRouteInterval = &dnsRouteInterval
}
+ providedSetupKey, err := getSetupKey()
+ if err != nil {
+ return err
+ }
+
config, err := internal.UpdateOrCreateConfig(ic)
if err != nil {
return fmt.Errorf("get config file: %v", err)
@@ -161,7 +159,7 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command) error {
config, _ = internal.UpdateOldManagementURL(ctx, config, configPath)
- err = foregroundLogin(ctx, cmd, config, setupKey)
+ err = foregroundLogin(ctx, cmd, config, providedSetupKey)
if err != nil {
return fmt.Errorf("foreground login failed: %v", err)
}
@@ -206,8 +204,13 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
return nil
}
+ providedSetupKey, err := getSetupKey()
+ if err != nil {
+ return err
+ }
+
loginRequest := proto.LoginRequest{
- SetupKey: setupKey,
+ SetupKey: providedSetupKey,
ManagementUrl: managementURL,
AdminURL: adminURL,
NatExternalIPs: natExternalIPs,
From 15eb752a7d574b33506cce9d90f4ff0b3700b827 Mon Sep 17 00:00:00 2001
From: Viktor Liu <17948409+lixmal@users.noreply.github.com>
Date: Sun, 11 Aug 2024 15:01:04 +0200
Subject: [PATCH 70/77] [misc] Update bug-issue-report.md to include anon flag
(#2412)
---
.github/ISSUE_TEMPLATE/bug-issue-report.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/bug-issue-report.md b/.github/ISSUE_TEMPLATE/bug-issue-report.md
index 8971972a270..789c6197425 100644
--- a/.github/ISSUE_TEMPLATE/bug-issue-report.md
+++ b/.github/ISSUE_TEMPLATE/bug-issue-report.md
@@ -31,9 +31,9 @@ Please specify whether you use NetBird Cloud or self-host NetBird's control plan
`netbird version`
-**NetBird status -d output:**
+**NetBird status -dA output:**
-If applicable, add the `netbird status -d' command output.
+If applicable, add the `netbird status -dA' command output.
**Do you face any client issues on desktop?**
From 539480a713c621917fd1de348ada8cc0ffe41e8f Mon Sep 17 00:00:00 2001
From: Bethuel Mmbaga
Date: Mon, 12 Aug 2024 13:48:05 +0300
Subject: [PATCH 71/77] [management] Prevent removal of All group from peers
during user groups propagation (#2410)
* Prevent removal of "All" group from peers
* Prevent adding "All" group to users and setup keys
* Refactor setup key group validation
---
management/server/account.go | 2 +-
management/server/setupkey.go | 23 +++++++++++++++---
management/server/setupkey_test.go | 39 ++++++++++++++++++++++++++----
management/server/user.go | 6 ++++-
4 files changed, 59 insertions(+), 11 deletions(-)
diff --git a/management/server/account.go b/management/server/account.go
index ca53ebad04b..97227274620 100644
--- a/management/server/account.go
+++ b/management/server/account.go
@@ -922,7 +922,7 @@ func (a *Account) UserGroupsAddToPeers(userID string, groups ...string) {
func (a *Account) UserGroupsRemoveFromPeers(userID string, groups ...string) {
for _, gid := range groups {
group, ok := a.Groups[gid]
- if !ok {
+ if !ok || group.Name == "All" {
continue
}
update := make([]string, 0, len(group.Peers))
diff --git a/management/server/setupkey.go b/management/server/setupkey.go
index 8ef91755c15..859f1b0b918 100644
--- a/management/server/setupkey.go
+++ b/management/server/setupkey.go
@@ -223,10 +223,8 @@ func (am *DefaultAccountManager) CreateSetupKey(ctx context.Context, accountID s
return nil, err
}
- for _, group := range autoGroups {
- if _, ok := account.Groups[group]; !ok {
- return nil, status.Errorf(status.NotFound, "group %s doesn't exist", group)
- }
+ if err := validateSetupKeyAutoGroups(account, autoGroups); err != nil {
+ return nil, err
}
setupKey := GenerateSetupKey(keyName, keyType, keyDuration, autoGroups, usageLimit, ephemeral)
@@ -279,6 +277,10 @@ func (am *DefaultAccountManager) SaveSetupKey(ctx context.Context, accountID str
return nil, status.Errorf(status.NotFound, "setup key not found")
}
+ if err := validateSetupKeyAutoGroups(account, keyToSave.AutoGroups); err != nil {
+ return nil, err
+ }
+
// only auto groups, revoked status, and name can be updated for now
newKey := oldKey.Copy()
newKey.Name = keyToSave.Name
@@ -399,3 +401,16 @@ func (am *DefaultAccountManager) GetSetupKey(ctx context.Context, accountID, use
return foundKey, nil
}
+
+func validateSetupKeyAutoGroups(account *Account, autoGroups []string) error {
+ for _, group := range autoGroups {
+ g, ok := account.Groups[group]
+ if !ok {
+ return status.Errorf(status.NotFound, "group %s doesn't exist", group)
+ }
+ if g.Name == "All" {
+ return status.Errorf(status.InvalidArgument, "can't add All group to the setup key")
+ }
+ }
+ return nil
+}
diff --git a/management/server/setupkey_test.go b/management/server/setupkey_test.go
index 034f4e2d636..aa5075b024e 100644
--- a/management/server/setupkey_test.go
+++ b/management/server/setupkey_test.go
@@ -26,10 +26,17 @@ func TestDefaultAccountManager_SaveSetupKey(t *testing.T) {
t.Fatal(err)
}
- err = manager.SaveGroup(context.Background(), account.Id, userID, &nbgroup.Group{
- ID: "group_1",
- Name: "group_name_1",
- Peers: []string{},
+ err = manager.SaveGroups(context.Background(), account.Id, userID, []*nbgroup.Group{
+ {
+ ID: "group_1",
+ Name: "group_name_1",
+ Peers: []string{},
+ },
+ {
+ ID: "group_2",
+ Name: "group_name_2",
+ Peers: []string{},
+ },
})
if err != nil {
t.Fatal(err)
@@ -70,6 +77,19 @@ func TestDefaultAccountManager_SaveSetupKey(t *testing.T) {
assert.NotEmpty(t, ev.Meta["key"])
assert.Equal(t, userID, ev.InitiatorID)
assert.Equal(t, key.Id, ev.TargetID)
+
+ groupAll, err := account.GetGroupAll()
+ assert.NoError(t, err)
+
+ // saving setup key with All group assigned to auto groups should return error
+ autoGroups = append(autoGroups, groupAll.ID)
+ _, err = manager.SaveSetupKey(context.Background(), account.Id, &SetupKey{
+ Id: key.Id,
+ Name: newKeyName,
+ Revoked: revoked,
+ AutoGroups: autoGroups,
+ }, userID)
+ assert.Error(t, err, "should not save setup key with All group assigned in auto groups")
}
func TestDefaultAccountManager_CreateSetupKey(t *testing.T) {
@@ -102,6 +122,9 @@ func TestDefaultAccountManager_CreateSetupKey(t *testing.T) {
t.Fatal(err)
}
+ groupAll, err := account.GetGroupAll()
+ assert.NoError(t, err)
+
type testCase struct {
name string
@@ -134,8 +157,14 @@ func TestDefaultAccountManager_CreateSetupKey(t *testing.T) {
expectedGroups: []string{"FAKE"},
expectedFailure: true,
}
+ testCase3 := testCase{
+ name: "Create Setup Key should fail because of All group",
+ expectedKeyName: "my-test-key",
+ expectedGroups: []string{groupAll.ID},
+ expectedFailure: true,
+ }
- for _, tCase := range []testCase{testCase1, testCase2} {
+ for _, tCase := range []testCase{testCase1, testCase2, testCase3} {
t.Run(tCase.name, func(t *testing.T) {
key, err := manager.CreateSetupKey(context.Background(), account.Id, tCase.expectedKeyName, SetupKeyReusable, expiresIn,
tCase.expectedGroups, SetupKeyUnlimitedUsage, userID, false)
diff --git a/management/server/user.go b/management/server/user.go
index 8fd0a16273e..727bc5c6b81 100644
--- a/management/server/user.go
+++ b/management/server/user.go
@@ -944,10 +944,14 @@ func validateUserUpdate(account *Account, initiatorUser, oldUser, update *User)
}
for _, newGroupID := range update.AutoGroups {
- if _, ok := account.Groups[newGroupID]; !ok {
+ group, ok := account.Groups[newGroupID]
+ if !ok {
return status.Errorf(status.InvalidArgument, "provided group ID %s in the user %s update doesn't exist",
newGroupID, update.Id)
}
+ if group.Name == "All" {
+ return status.Errorf(status.InvalidArgument, "can't add All group to the user")
+ }
}
return nil
From 9716be854d40540196aa5aaa818fbfe3b84ffebe Mon Sep 17 00:00:00 2001
From: Maycon Santos
Date: Tue, 13 Aug 2024 16:20:06 +0200
Subject: [PATCH 72/77] [client] Upgrade fyne version to fix freezing routes
window (#2417)
---
go.mod | 33 ++--
go.sum | 515 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 498 insertions(+), 50 deletions(-)
diff --git a/go.mod b/go.mod
index 25e726769ef..f47d8cf7928 100644
--- a/go.mod
+++ b/go.mod
@@ -30,7 +30,7 @@ require (
)
require (
- fyne.io/fyne/v2 v2.1.4
+ fyne.io/fyne/v2 v2.5.0
fyne.io/systray v1.11.0
github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible
github.com/c-robinson/iplib v1.0.3
@@ -38,7 +38,7 @@ require (
github.com/coreos/go-iptables v0.7.0
github.com/creack/pty v1.1.18
github.com/eko/gocache/v3 v3.1.1
- github.com/fsnotify/fsnotify v1.6.0
+ github.com/fsnotify/fsnotify v1.7.0
github.com/gliderlabs/ssh v0.3.4
github.com/godbus/dbus/v5 v5.1.0
github.com/golang/mock v1.6.0
@@ -82,7 +82,7 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.26.0
goauthentik.io/api/v3 v3.2023051.3
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
- golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
+ golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a
golang.org/x/net v0.26.0
golang.org/x/oauth2 v0.19.0
golang.org/x/sync v0.7.0
@@ -100,7 +100,7 @@ require (
cloud.google.com/go/compute/metadata v0.3.0 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
- github.com/BurntSushi/toml v1.3.2 // indirect
+ github.com/BurntSushi/toml v1.4.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.12.3 // indirect
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect
@@ -119,20 +119,25 @@ require (
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
- github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect
- github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f // indirect
- github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be // indirect
+ github.com/fredbi/uri v1.1.0 // indirect
+ github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
+ github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a // indirect
+ github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect
+ github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
+ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
+ github.com/go-text/render v0.1.0 // indirect
+ github.com/go-text/typesetting v0.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
- github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.3 // indirect
+ github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
@@ -140,9 +145,11 @@ require (
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
+ github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/native v1.1.0 // indirect
+ github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/kelseyhightower/envconfig v1.4.0 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect
@@ -154,6 +161,7 @@ require (
github.com/moby/sys/user v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
+ github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
@@ -168,21 +176,24 @@ require (
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.53.0 // indirect
github.com/prometheus/procfs v0.15.0 // indirect
+ github.com/rymdport/portal v0.2.2 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/spf13/cast v1.5.0 // indirect
- github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
- github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
+ github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
+ github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
- github.com/yuin/goldmark v1.4.13 // indirect
+ github.com/yuin/goldmark v1.7.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
go.opentelemetry.io/otel/sdk v1.26.0 // indirect
go.opentelemetry.io/otel/trace v1.26.0 // indirect
golang.org/x/image v0.18.0 // indirect
+ golang.org/x/mod v0.17.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
+ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240509183442-62759503f434 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
diff --git a/go.sum b/go.sum
index 2e2cadd43a5..06df95a33a5 100644
--- a/go.sum
+++ b/go.sum
@@ -1,17 +1,55 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
+cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
+cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
+cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go/auth v0.3.0 h1:PRyzEpGfx/Z9e8+lHsbkoUVXD0gnu4MNmm7Gp8TQNIs=
cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w=
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cunicu.li/go-rosenpass v0.4.0 h1:LtPtBgFWY/9emfgC4glKLEqS0MJTylzV6+ChRhiZERw=
cunicu.li/go-rosenpass v0.4.0/go.mod h1:MPbjH9nxV4l3vEagKVdFNwHOketqgS5/To1VYJplf/M=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
-fyne.io/fyne/v2 v2.1.4 h1:bt1+28++kAzRzPB0GM2EuSV4cnl8rXNX4cjfd8G06Rc=
-fyne.io/fyne/v2 v2.1.4/go.mod h1:p+E/Dh+wPW8JwR2DVcsZ9iXgR9ZKde80+Y+40Is54AQ=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+fyne.io/fyne/v2 v2.5.0 h1:lEjEIso0Vi4sJXYngIMoXOM6aUjqnPjK7pBpxRxG9aI=
+fyne.io/fyne/v2 v2.5.0/go.mod h1:9D4oT3NWeG+MLi/lP7ItZZyujHC/qqMJpoGTAYX5Uqc=
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
@@ -19,10 +57,9 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
-github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
-github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
-github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
+github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
+github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.12.3 h1:LS9NXqXhMoqNCplK1ApmVSfB4UnVLRDWRapB6EIlxE0=
@@ -34,13 +71,18 @@ github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible h1:hqcTK6ZISdip65SR792lwYJT
github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible/go.mod h1:6B1nuc1MUs6c62ODZDl7hVE5Pv7O2XGSkgg2olnq34I=
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 h1:pami0oPhVosjOu/qRHepRmdjD6hGILF7DBr+qQZeP10=
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2/go.mod h1:jNIx5ykW1MroBuaTja9+VpglmaJOUzezumfhLlER3oY=
-github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9IrprcQfI=
github.com/allegro/bigcache/v3 v3.0.2/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I=
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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/c-robinson/iplib v1.0.3 h1:NG0UF0GoEsrC1/vyfX1Lx2Ss7CySWl3KqqXh3q4DdPU=
@@ -52,10 +94,15 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/containerd/containerd v1.7.16 h1:7Zsfe8Fkj4Wi2My6DXGQ87hiqIrmOXolm72ZEkFU5Mg=
github.com/containerd/containerd v1.7.16/go.mod h1:NL49g7A/Fui7ccmxV6zkBWwqMgmMxFWzujYCc+JLt7k=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
@@ -64,9 +111,11 @@ github.com/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu
github.com/coocood/freecache v1.2.1/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk=
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
-github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@@ -99,29 +148,45 @@ github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb
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.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
+github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
-github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
-github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
+github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8=
+github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
-github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe h1:A/wiwvQ0CAjPkuJytaD+SsXkPU0asQ+guQEIg1BJGX4=
+github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg=
+github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a h1:ybgRdYvAHTn93HW79bLiBiJwVL4jVeyGQRZMgImoeWs=
+github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a/go.mod h1:gsGA2dotD4v0SR6PmPCYvS9JuOeMwAtmfvDE7mbYXMY=
+github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 h1:hnLq+55b7Zh7/2IRzWCpiTcAvjv/P8ERF+N7+xXbZhk=
+github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw=
github.com/gliderlabs/ssh v0.3.4/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914=
-github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f h1:s0O46d8fPwk9kU4k1jj76wBquMVETx7uveQD9MCIQoU=
-github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be h1:Z28GdQBfKOL8tNHjvaDn3wHDO7AzTRkmAXvHvnopp98=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
+github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
@@ -142,30 +207,46 @@ github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZs
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/go-text/render v0.1.0 h1:osrmVDZNHuP1RSu3pNG7Z77Sd2xSbcb/xWytAj9kyVs=
+github.com/go-text/render v0.1.0/go.mod h1:jqEuNMenrmj6QRnkdpeaP0oKGFLDNhDkVKwGjsWWYU4=
+github.com/go-text/typesetting v0.1.0 h1:vioSaLPYcHwPEPLT7gsjCGDCoYSbljxoHJzMnKwVvHw=
+github.com/go-text/typesetting v0.1.0/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
+github.com/go-text/typesetting-utils v0.0.0-20240329101916-eee87fb235a3 h1:levTnuLLUmpavLGbJYLJA7fQnKeS7P1eCdAlM+vReXk=
+github.com/go-text/typesetting-utils v0.0.0-20240329101916-eee87fb235a3/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
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=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
-github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@@ -175,18 +256,25 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
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/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -197,8 +285,25 @@ github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSN
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/nftables v0.0.0-20220808154552-2eca00135732 h1:csc7dT82JiSLvq4aMyQMIQDL7986NH6Wxf/QrvOj55A=
github.com/google/nftables v0.0.0-20220808154552-2eca00135732/go.mod h1:b97ulCCFipUC+kSin+zygkvUVpx0vyIAwxXFdY3PlNc=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
+github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -207,32 +312,60 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA=
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gopacket/gopacket v1.1.1 h1:zbx9F9d6A7sWNkFKrvMBZTfGgxFoY4NgUudFVVHMfcw=
github.com/gopacket/gopacket v1.1.1/go.mod h1:HavMeONEl7W9036of9LbSWoonqhH7HA1+ZRO+rMIvFs=
-github.com/gopherjs/gopherjs v0.0.0-20220410123724-9e86199038b0 h1:fWY+zXdWhvWndXqnMj4SyC/vi8sK508OjhGCtMzsA9M=
-github.com/gopherjs/gopherjs v0.0.0-20220410123724-9e86199038b0/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI=
+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/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357 h1:Fkzd8ktnpOR9h47SXHe2AYPwelXLH2GjGsjlAloiWfo=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
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=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
@@ -243,17 +376,22 @@ github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
-github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
+github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 h1:Po+wkNdMmN+Zj1tDsJQy7mJlPlwGNQd9JZoPjObagf8=
+github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49/go.mod h1:YiutDnxPRLk5DLUFj6Rw4pRBBURZY07GFr54NdV9mQg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
-github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
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/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
+github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
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/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
@@ -264,6 +402,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -274,13 +413,15 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
+github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
@@ -290,12 +431,21 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
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/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
@@ -317,6 +467,8 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc=
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ=
+github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
+github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944 h1:TDtJKmM6Sf8uYFx/dMeqNOL90KUoRscdfpFZ3Im89uk=
github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944/go.mod h1:sHA6TRxjQ6RLbnI+3R4DZo2Eseg/iKiPRfNmcuNySVQ=
github.com/netbirdio/ice/v3 v3.0.0-20240315174635-e72a50fcb64e h1:PURA50S8u4mF6RrkYYCAvvPCixhqqEiEy3Ej6avh04c=
@@ -327,8 +479,8 @@ github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/netbirdio/wireguard-go v0.0.0-20240105182236-6c340dd55aed h1:t0UADZUJDaaZgfKrt8JUPrOLL9Mg/ryjP85RAH53qgs=
github.com/netbirdio/wireguard-go v0.0.0-20240105182236-6c340dd55aed/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
-github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
+github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
@@ -354,10 +506,12 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY8d4=
github.com/pegasus-kv/thrift v0.13.0/go.mod h1:Gl9NT/WHG6ABm6NsrbfE8LiJN0sAyneCrvB4qN4NPqQ=
+github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
@@ -379,12 +533,16 @@ github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouAN
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
github.com/pion/turn/v3 v3.0.1 h1:wLi7BTQr6/Q20R0vt/lHbjv6y4GChFtC33nkYbasoT8=
github.com/pion/turn/v3 v3.0.1/go.mod h1:MrJDKgqryDyWy1/4NT9TWfXWGMC7UHT6pJIv1+gMeNE=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+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 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
+github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
+github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
@@ -397,6 +555,8 @@ github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+a
github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
github.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek=
github.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so=
@@ -405,35 +565,47 @@ github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/rymdport/portal v0.2.2 h1:P2Q/4k673zxdFAsbD8EESZ7psfuO6/4jNu6EDrDICkM=
+github.com/rymdport/portal v0.2.2/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
+github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
+github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.13.0 h1:Dx1kYM01xsSqKPno3aqLnrwac2LetPvN23diwyr69Qs=
github.com/smartystreets/assertions v1.13.0/go.mod h1:wDmR7qL282YbGsPy6H/yAsesrxfxaaSlJazyFLYVFx8=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
-github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
+github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
+github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
-github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
-github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=
-github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
+github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
+github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
+github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
+github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
+github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -444,6 +616,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@@ -452,6 +625,7 @@ github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U=
github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI=
github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0 h1:isAwFS3KNKRbJMbWv+wolWqOFUECmjYZ+sIRZCIBc/E=
@@ -466,22 +640,34 @@ github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYg
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
-github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/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.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
+github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
+github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zcalusic/sysinfo v1.0.2 h1:nwTTo2a+WQ0NXwo0BGRojOJvJ/5XKvQih+2RrtWqfxc=
github.com/zcalusic/sysinfo v1.0.2/go.mod h1:kluzTYflRWo6/tXVMJPdEjShsbPpsFRyy+p1mBQPC30=
+go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
+go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
@@ -506,12 +692,20 @@ go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2L
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
goauthentik.io/api/v3 v3.2023051.3 h1:NebAhD/TeTWNo/9X3/Uj+rM5fG1HaiLOlKTNLQv9Qq4=
goauthentik.io/api/v3 v3.2023051.3/go.mod h1:nYECml4jGbp/541hj8GcylKQG1gVBsKppHy4+7G8u4U=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
@@ -520,41 +714,93 @@ golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
+golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
+golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a h1:sYbmY3FwUWCBTodZL1S3JUuOvaW6kM2o+clDzzDNBWg=
+golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a/go.mod h1:Ede7gF0KGoHlj822RtphAHK1jLdrcuRBZg0sF1Q+SPc=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
@@ -564,51 +810,99 @@ golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -629,9 +923,14 @@ golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
@@ -639,6 +938,9 @@ golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -647,16 +949,61 @@ golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -667,23 +1014,102 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvY
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
+google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
+google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
+google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
+google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/api v0.177.0 h1:8a0p/BbPa65GlqGWtUKxot4p0TV8OGOfyTjtmkXNXmk=
google.golang.org/api v0.177.0/go.mod h1:srbhue4MLjkjbkux5p3dw/ocYOSZTaIEvf7bCOnFQDw=
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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto/googleapis/api v0.0.0-20240509183442-62759503f434 h1:OpXbo8JnN8+jZGPrL4SSfaDjSCjupr8lXyBAbexEm/U=
google.golang.org/genproto/googleapis/api v0.0.0-20240509183442-62759503f434/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
@@ -694,6 +1120,7 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
@@ -704,13 +1131,14 @@ google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh
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-20200227125254-8fa46927fb4f/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
@@ -730,6 +1158,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
@@ -743,7 +1172,12 @@ gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ=
k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
@@ -751,5 +1185,8 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
From 4bbedb5193f13f1e033acd4657a9c4f41d048bde Mon Sep 17 00:00:00 2001
From: Foosec <31885466+Foosec@users.noreply.github.com>
Date: Tue, 13 Aug 2024 17:07:44 +0200
Subject: [PATCH 73/77] [client] Add mTLS support for SSO login (#2188)
* Add mTLS support for SSO login
* Refactor variable to follow Go naming conventions
---------
Co-authored-by: bcmmbaga
---
client/android/login.go | 2 +-
client/internal/auth/oauth.go | 2 +-
client/internal/auth/pkce_flow.go | 13 +++++++++++++
client/internal/config.go | 30 ++++++++++++++++++++++++++++++
client/internal/pkce_auth.go | 6 +++++-
client/ios/NetBirdSDK/login.go | 2 +-
6 files changed, 51 insertions(+), 4 deletions(-)
diff --git a/client/android/login.go b/client/android/login.go
index 3840c75c112..bb61edfa8e2 100644
--- a/client/android/login.go
+++ b/client/android/login.go
@@ -84,7 +84,7 @@ func (a *Auth) SaveConfigIfSSOSupported(listener SSOListener) {
func (a *Auth) saveConfigIfSSOSupported() (bool, error) {
supportsSSO := true
err := a.withBackOff(a.ctx, func() (err error) {
- _, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
+ _, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL, nil)
if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.NotFound || s.Code() == codes.Unimplemented) {
_, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
s, ok := gstatus.FromError(err)
diff --git a/client/internal/auth/oauth.go b/client/internal/auth/oauth.go
index c9f10ca863a..001609f2615 100644
--- a/client/internal/auth/oauth.go
+++ b/client/internal/auth/oauth.go
@@ -86,7 +86,7 @@ func NewOAuthFlow(ctx context.Context, config *internal.Config, isLinuxDesktopCl
// authenticateWithPKCEFlow initializes the Proof Key for Code Exchange flow auth flow
func authenticateWithPKCEFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) {
- pkceFlowInfo, err := internal.GetPKCEAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
+ pkceFlowInfo, err := internal.GetPKCEAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL, config.ClientCertKeyPair)
if err != nil {
return nil, fmt.Errorf("getting pkce authorization flow info failed with error: %v", err)
}
diff --git a/client/internal/auth/pkce_flow.go b/client/internal/auth/pkce_flow.go
index 32f5383d36e..71ff6de4134 100644
--- a/client/internal/auth/pkce_flow.go
+++ b/client/internal/auth/pkce_flow.go
@@ -4,6 +4,7 @@ import (
"context"
"crypto/sha256"
"crypto/subtle"
+ "crypto/tls"
"encoding/base64"
"errors"
"fmt"
@@ -143,6 +144,18 @@ func (p *PKCEAuthorizationFlow) WaitToken(ctx context.Context, _ AuthFlowInfo) (
func (p *PKCEAuthorizationFlow) startServer(server *http.Server, tokenChan chan<- *oauth2.Token, errChan chan<- error) {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
+ cert := p.providerConfig.ClientCertPair
+ if cert != nil {
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{
+ Certificates: []tls.Certificate{*cert},
+ },
+ }
+ sslClient := &http.Client{Transport: tr}
+ ctx := context.WithValue(req.Context(), oauth2.HTTPClient, sslClient)
+ req = req.WithContext(ctx)
+ }
+
token, err := p.handleRequest(req)
if err != nil {
renderPKCEFlowTmpl(w, err)
diff --git a/client/internal/config.go b/client/internal/config.go
index 461dcdd9650..725703c437f 100644
--- a/client/internal/config.go
+++ b/client/internal/config.go
@@ -2,6 +2,7 @@ package internal
import (
"context"
+ "crypto/tls"
"fmt"
"net/url"
"os"
@@ -57,6 +58,8 @@ type ConfigInput struct {
DisableAutoConnect *bool
ExtraIFaceBlackList []string
DNSRouteInterval *time.Duration
+ ClientCertPath string
+ ClientCertKeyPath string
}
// Config Configuration type
@@ -102,6 +105,13 @@ type Config struct {
// DNSRouteInterval is the interval in which the DNS routes are updated
DNSRouteInterval time.Duration
+ //Path to a certificate used for mTLS authentication
+ ClientCertPath string
+
+ //Path to corresponding private key of ClientCertPath
+ ClientCertKeyPath string
+
+ ClientCertKeyPair *tls.Certificate `json:"-"`
}
// ReadConfig read config file and return with Config. If it is not exists create a new with default values
@@ -385,6 +395,26 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) {
}
+ if input.ClientCertKeyPath != "" {
+ config.ClientCertKeyPath = input.ClientCertKeyPath
+ updated = true
+ }
+
+ if input.ClientCertPath != "" {
+ config.ClientCertPath = input.ClientCertPath
+ updated = true
+ }
+
+ if config.ClientCertPath != "" && config.ClientCertKeyPath != "" {
+ cert, err := tls.LoadX509KeyPair(config.ClientCertPath, config.ClientCertKeyPath)
+ if err != nil {
+ log.Error("Failed to load mTLS cert/key pair: ", err)
+ } else {
+ config.ClientCertKeyPair = &cert
+ log.Info("Loaded client mTLS cert/key pair")
+ }
+ }
+
return updated, nil
}
diff --git a/client/internal/pkce_auth.go b/client/internal/pkce_auth.go
index a35dacc77be..6f714889fea 100644
--- a/client/internal/pkce_auth.go
+++ b/client/internal/pkce_auth.go
@@ -2,6 +2,7 @@ package internal
import (
"context"
+ "crypto/tls"
"fmt"
"net/url"
@@ -36,10 +37,12 @@ type PKCEAuthProviderConfig struct {
RedirectURLs []string
// UseIDToken indicates if the id token should be used for authentication
UseIDToken bool
+ //ClientCertPair is used for mTLS authentication to the IDP
+ ClientCertPair *tls.Certificate
}
// GetPKCEAuthorizationFlowInfo initialize a PKCEAuthorizationFlow instance and return with it
-func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL *url.URL) (PKCEAuthorizationFlow, error) {
+func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL *url.URL, clientCert *tls.Certificate) (PKCEAuthorizationFlow, error) {
// validate our peer's Wireguard PRIVATE key
myPrivateKey, err := wgtypes.ParseKey(privateKey)
if err != nil {
@@ -93,6 +96,7 @@ func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL
Scope: protoPKCEAuthorizationFlow.GetProviderConfig().GetScope(),
RedirectURLs: protoPKCEAuthorizationFlow.GetProviderConfig().GetRedirectURLs(),
UseIDToken: protoPKCEAuthorizationFlow.GetProviderConfig().GetUseIDToken(),
+ ClientCertPair: clientCert,
},
}
diff --git a/client/ios/NetBirdSDK/login.go b/client/ios/NetBirdSDK/login.go
index 3572aa310c2..ff637edd48a 100644
--- a/client/ios/NetBirdSDK/login.go
+++ b/client/ios/NetBirdSDK/login.go
@@ -74,7 +74,7 @@ func (a *Auth) SaveConfigIfSSOSupported() (bool, error) {
err := a.withBackOff(a.ctx, func() (err error) {
_, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.NotFound || s.Code() == codes.Unimplemented) {
- _, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
+ _, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL, nil)
if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.NotFound || s.Code() == codes.Unimplemented) {
supportsSSO = false
err = nil
From 181dd93695f62fbe748aab40c7582e8173fd0af7 Mon Sep 17 00:00:00 2001
From: Viktor Liu <17948409+lixmal@users.noreply.github.com>
Date: Wed, 14 Aug 2024 12:15:02 +0200
Subject: [PATCH 74/77] [client] Update png systray disconnected icon (#2428)
---
client/ui/netbird-systemtray-disconnected.png | Bin 9258 -> 9816 bytes
...netbird-systemtray-update-disconnected.png | Bin 11929 -> 12437 bytes
2 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/client/ui/netbird-systemtray-disconnected.png b/client/ui/netbird-systemtray-disconnected.png
index 0e1b7275f3f048770befda090aba02ee1deb4de8..3aae73231cf22b82a85962695cc5af29885ec802 100644
GIT binary patch
delta 5938
zcmZ8lc|26__rJ!-n4}?V2$L<0nK5?R_kGD$$k-J^*%GduP(-E3R+fY;Wy|`(AiFkW
zmz^w$h8n|ne7>*mAHU!I@80{|bDn$N@AE$AJa6=fMrqn0b0PZVz`
zG@~jez$$yZ+N=_K*)>mN6LDgYCHr-yIQL^OhE|$UbCEyIr@3qGP78C
zfwkw?e-=NTEzs9YpPv_PVCOlsEmv%i|FJ$N28-qc`G=THk|ZtWkJB}hInO>M?`+L1
zZ1K)jWapTnpx6$pzoofM-@b=~wY;ytb-&fOW)aXyz7mD@&soN#+qNUMU}@%rQ+-v4S5_AZ
zT3MuX7mfsa(CLQ4W2ZP3md&H!A9rGo|JlAZ*s&O!&1wSVpzA1D2sAiKXRxj<)u+iH
z0?myQuUM-567}5llNSmV3f=Ricec##`5x!qAnnrz_}nfj`HY)eR*rhmk`mVBB0#I9F*mcPs|yj=?EpVCjlQrEs!Rau`{xEJjXNK?Vuu
z=jYeeH#SqWcDsG&hVLz9q?9BUBZ-sEIHt2ykype@TVk<_SUE*$tT;wW5rg^nix3|_
zMPpyr8}4_toC4ga-(aMq6*2#R^}9!>c4#v4=0$KX8r`%l$_MruOR#Ji%Z)sLo+DFwPS_V~Tt$-%A$*M~uqon?NVNNC$ZYJJ>RELX13+Fm9#QOFKT2|F|&e^ftXl;IIy`d
z$;+m}h6HItzkl3dXIjo3)8Dvp`zBl#{&XVIR%{i
z@kvE090fqJl+v9Y){B8JF8whUKA-UImUSF4;K4#`Gz!2-_LDn~qx#4BGFaO>6VviL
zdXJ`_^PT5DKE(`SWCTC%+260%mu7NJv`|FWacVa4(J#~fNsKZ{c1_|ByhpA0%`(gA5ZpC3^;
zsCx^~ZhHs^Vmm6zGIV)Owr{WTsY^2=fF9oI^&hW+$e`t7uw-V%;83AEBwpeu&_&e}
z;BPKhy|W`^ljLP|za=pqThvw`u|ZD*6u5=!{sr&rJ&WW2>q#M=wFC0O%Hc1=Ba1j3
z+4ln3C;p;Q{uOr*$=uueGlY4$jEe3Es*Buo(0x7rA|W|3wjg=df2+mM`LJ=i^{g_0
zcs7a4Xanag@Xa1l19xjHd^HQznx-0q)~eD6LG_Qm2Qy$oHD4%@fAN+>&Sis_e+(re
zIysIdT~xhk)O3?)-S)6o;7pf`w<-4>xMrL-5>UXZ-%e*lfBZ(#_viLW0a5yV$+IkB&Z#bRIoFah3wXymM$q)u@?LHiP6Zv
zeBDHsp+9YHi2SJ<6X5OjkUqWd)z`Uez{qaLMTpe;YaFc&LJtnqa>IK$!s
zw$yoW5*q4}?B&AukL)ge=#f80C+u+7S+l+SoN>G%c9+qxKfR@GayKDI@FIIRa^*#M
z#<|}aj~#_A;-vH7SF~E4H>#Zvb1nA7oj2aDbwnnFNr+Rp+7v7#c()flu9lp3fbIM;
z5YBXTGT?0=9nSA)XJ>WEGjz^FxCHBVgG2VR-|tLkLJn+4wOqhhn9wFUzb4x4mUM1D;<9Rhu$TsZR)^QbMTswKv%!*
z#e}czqSJrzV;ZQ$V2vNqvrmM&-6qkBrkY`cl#m-NS%)d
z8VxQiL@Z>SaP`FV&E5ThL8IyR`TFt>ug>MN1$aJTV{h?{jebDvxo65@+BQ0l=p5~Z
z;SR#^pBRpA14p<6zz_Mz5gl65nl|W$-*U6stbJaOG=}p8|~E1ANLe><&G|M>x+r0+K1`@
zvKb%UMCC?w!H1`ZZO_%T4OKhN!SSd&(o6M%)pYhO_pVCbYm$sWzAPRqEE;Ant+Uk}
znP>MIbZL7i4j^B0Z=3LJ1rpIa#Mj~P&x298*v?YFe7>)0Jmac`-Hy6!0Q2ZhhR614
z=T}MoXES>wo0;A6NWuwnwS`9_%yDsP}}~2^LG$N@v6Tb3NfgeU2q=bvVVKKE5elx4
z-VWsA@KB=hn;Mk|!ByR6ouncc4C{Sia?j{N3@o7fpb^KXyMVT!;g2peW3>bwWSbPD
zVBV6H7WwXoY8b$5-kFK+Fcaii|N4RLRsB+&+pp{p{NqGDL^x|ONd(@=(7?r+6stMq
z(xxWIW($uDF6(Pn%ywA`T5%%MTikyC+uPeipffI3nB
zb*aCcl|}0{DH|ip{ZO9FE8piX%)cmF&(e0Y#Cv-acO^Yc#L$;v_bEpATcI~Q%;(9r>vkTXT~EZOg}hn5
zk*~1A_-s{mUNsx2cDe!XlbRX*@b0EQ%6ukTSoSD|otk0at8pNW=A#GFE%Coy+Je$F
zKHb)cm6Xgz|DcP6z10|Y?T*{KENNaTTh4bztXnyusl@2`g-%{?BHPohedgUv@(gN1q1bz&v&ZCdbZA89h!zH6~M&x)=6g8_f-
zTa8?|5=uZ#vt@maz-ylUyLKvNG6^-GSH)aw25kgS>tFi8#Q6N68AL)OMZ@1}%xFCM
zWb)bT+Gm8NR_mIJY8hR=n`rte=GQhqzSwu~BtG#;<2{G?=b+qpd{&Xh^L`}1Y}TGj
zSHB@<#NN&pJmVC`me09QCEY?D;hTX=UEw{3FBVe=?`q+gkIUjPYcf`7^qSE@dSB{O
zz3*0&!}xUtnl<_T>+;y67Z0$t6In9%pG4N%cFxyV!0|(6b=_Y=eU}9r;YHya9xmwi
zx@yOHDBc*#GACwv*S=e<{xux)by++RqW0cd6xjgU)%@Nq*|`f&mH}6MS|OX)(}QA&
zo}$R2m%loNU~1XV@O*bf&VzQKXgHl<-THYCaKxr}x8A
z{r{GMj~ox$o?pT5vYi?~y54uDDn7{d;%@P)Q
z21JgO%V9}B0+_|LAKctaHJrrn($Q3DL2sI-QJM;vt3#;ZZZ&KRUWzXb(I@Lw2TVz$
zX;LU3bfuwx23Ido^g4*XxYAI$-Ko9B=2wf`gouS=;+?xS1bw;$vCf++s!R3X;Tnd`>@&(Chhf|AbrC{71l21AHvYS-2c`mvO?Ao
z0nlo<+i5He*Zq5|j&BI;sAumaqN9zL{m#ov=i~k>Ni2MF;FMz76JVh-)=hN&pG90i
zVcIoQm{5ah%`$x2tSX)wr=vKep&Wn*iyS54i;2#z78f{%PisO)<-G3w8Xfby`ov
zgdj0Y9r%EE-579lh)4dunWLr2|JvM#Hf!3KMsR_zScrVCfT01USWcjdtMfJ=cDh-K
zzjT=V3bG1DexE-Wzx9xLVN+#i4jtTjMz5227J_h98Y#$({3#^!vl*0-{*m+Bk1crl
zq%jZx^1YbbC&M>>&=!{N(9^)op%_v!N0B8y1Ch6rS90=f)eJh}&(=X*v`$+-5k@h-
z=0ZmUYxk=Ta1~*W9L(fObSJ+W6PyU6qXAvonHs`>cU&>KxgF2V;Jc3%DXYCJ$^YP_
z7e7zzGlNne(O+`4+h#uP@l=5$0wI5Sc#
z--$(5(5Yd!6sDBpVAN<*g=;;$*mcNXfsKisA96~7@Xc6Ff1~fDab_-H^Xm!fwU%`r
z49yTr^*@?rC58D%@?`q<9^K$07eGd0JJ+TMFGN3|VO%0DA$VN=0^HnDeg1j6mi)lA
z8r95Sz1HBV_tl^s&86hf<+lChY)Yv}j693MD#GEdo^a
z2kF0$JwX2Y6+;`cN)O2wkbjXg_M9@d(C`9I&kyDS(pYOy+Nq2`jZsntzy_1B_XNDy~o6k|20lW
z^n2TXWKR9ZIn*~#E?=IB>s45iCSCrfyYpVujXZ|y5G7a1q1TdJMk-)amp07Wr}gn+
z()~P?Tvhs<8G|=+VF27db9p-zu9g7ro;vW-g@&Gf4ZS9c^c^lxPoeMw)1U7T+VJdc
zZpIYol^t#mhQ+Q>WC&7AMaMX5zT~{OOWsLY2wxf8^*k(qtX{a%9iiU4-Cr_c*gl;R
zGsqzZD38y+uXdcg2jWX#A9~){Byg-a1B=J9j{a%V
zHUcM#wufR80@IT~kM5$4>*N+_LuctFuJfSNEc((o*;KHUYIgiE2?#M&_BuZEDe%_(
zdpr>B(jQ|^gZ$IYmg0RTqa921t*yL1GhB#R=@hm$xRJGzk
z+q5Vnd+(Y*Nsu+SER1f}BLpQOu^S?;18&-Z^#f~nnnVv$?bqs!vAzdFjWVhPvE
zJsdB{I=%+>T)`BOGNF5rP91OrwNp-*HRR7JA`Ood{PZ`O
zX_juEfS)C%pF$~Tp;kfaKJ>B%ZS0Y0vw5ilCjhwP!_WQv_Vwc{$&XEpXvMGZFxe_r
z7pydD**ho0&1hEp?O#w&C&n6{^ff%5%{#jf7WaEN?Z&~W;qM`$d7hT_7jP!&4Xqq+
zY}U6$!Kf8N#SZ(IGZVv8^G9NdwB}Rhmyi*_^KZh`97E58-;*PUT)L>yccz|_tY#^V
zN%D3R(dPs1Qp@3KNIp74)Sqj7R*HGmpW3bRf0LmZJxES9sUYA_oN`xzf0F81R=dog
zC)`m!N!`odt@A|~TbLl^gkPSWLcdQ*KBo=|Pcs3R2-AYI)M^{~v_-W$;?(HV0=-lv
z^5!Q6S1`uusYC=Zj4Lrc>FEVYpBtht6Pve>ekS~v|I`;*%JP#WPXBE@&HqAA8Cd4>
zlB3d;s@G2`T;Y-Zt_L#LJVZO>V+J#|Im1&(-}b)T)mb+@nc(_<8l``KJUeTeY$|pf
iU(NTw^E1wNizB{L)T06V%{pIzdg*JMXuZ|INB;*}Gaxts
delta 5569
zcmZ8lc|26#|GqOO$x<`MGD-}xZ)3@pqRhxHQ`RsPB196|<_;yIEZNJxMV68+#7K!O
zO-j}np(qWqGiLn8=lkF9b^p2d^*ZOi&-;0v_jBIojF>$)O2UCW@6g=4*WwbUY}-X*
z)XN8Xbcj4!R
zH4&cvf5TQ?AK70vJ180MBz(}(G&7WH73#!&ml7AejIu|mJYv6p_BbWV$D!KNE;zG5
z0xzL7L050eE7{rr8#?37>eN)V)Ot~*%SO{+-Q&etZe`6QxDu@)
zIlh8(W&94eF->b+Pt;GyDkVgDzkQq7`TTBtZS~>mDD)Ntoc=piJTETERF
z`3^5paQFjcAI5i@XL_sD*MCy_aP6x~-aMq`lfbjn=d0yK$TyXm6qfs5X7%?WV;*$k
z1?8RkdP)sJT?1k3lQ}EV*qTe0%%Qb5txsL3&y8GlM-x^f
z{%Tbr(OcY(zmJ1ehx&+*C+9r}BwRd?y?(vu*(2iLbdK$zuFxyl7JO3a1qJBRwKek)
zmn{L5-D;@!(##s98*lsj*whpprEIm)OlhIFgwZ$6TF9cmX9I?YOIP&(;PBGv&dBE@Bs@*P&*o3$T7
zpCPS59kkWWWebarx4u-cdS#w9!;$Er@+I)Srb-({aX>V2vAy{&?HzKbf8?XqJ){`42uim76MQ(GESvR
zsO+sYc>4y?6$t^nNAIj`6~;QktCV4`cWN?TU6%@V3#YV5h(`e=YpXVXSO)70em%mt
zPAV-?a;u`}=YpsoC3%Tai{n?qR|moZE>tRF
z*E6{UY-8SBFF|2WBpKETCdWTCoNn5W{-c#Cnheo@g(P@O5wHIsD7P{tU_j>9b}aMq
ztyM057{Kcq@zcsaI)Gq(qx^6<>ACG|F`JM7rJr>UAO8l{H=}H5FOu;U?U|>-oZFZVg$El;E{KW)BwA}|`J9po^qMBOU>
z;IuaOgoP1V?JWUR`AfBv06>twQSpjbw*GE@Wqqzqj`3-lto0(XDPg<CDzNistR#DGWj|F`LB>k%Ddvr)dNneYTG#+
z%;&0_tTSo~7M7;V+0{P!99uU9hG0cSd7tZ8vL^Z4E3dayb)*LFQ^EZ}5?
z9l@!s0$Z(8|Gj;cv1xe7P%goEF=R4CQnjl7%nRJ4wf3ZJDek(7n`!EfvA2`83B#=g
zv!=0)smgBZO0WL@rd7R%`rJx7^c06upu~2_gf^oy=<|;e-sI;3+g7scU%&5mlu9P=
zJ?0JR{&`cHzLpjW4uYgYizk0+38G<5d#>CMLw$UL)S^YXavQS()Ac*4#*37RH%>zK
zdd^wPRN~3;!%EOYieTP+TD=rsNoWcQ
z%QqKGOFM7dnoxA)wFDs1$VlJ=u;^RbSFF7mgPV-{8Sw+rdYjeWgB?`)+xM7zSuG(g
zg}8LoPlbDjKqnjV;STf`apvR7-Nm8?Lf&q?8F8+?|llKShs%kL1%j7DVI6W(f(Dr$gL?bl(bXg*uH9hvumz;cH>PTL=
z4d~rCE-D7S!3&F2-#BK#X=u;@hA?gD>PF&Q6(*@gZH5Q(rJ;rE$E}|C{Ja^zv*3(@5cteu=BwJQ7@cz=)F?#@ut^U4K_x#S{%Bv-TXh`U&=Sw4REfvXv2bpRdNK=6U0zo_sbO4_FIrKYu$
zW(qsxwiEM%!th8}hz3wT=8=I?w+mbPnB%|cP**Ja(fqOQ0?E0Z`XT&ywef2H
z+i2}GJAR$5R6TykJTj6TkcNz7sx&eTUBOKNp>+c)^?k$i(!Ov6xpBO4Oj$lAJW}SZ
zp2$<>3Ru28^fhN^-z0Ujk{KS`eyo=K#Bf}h9|A{sHI9RCP0^spll4-VFa5IL!LZxS
zY)*AU<>&XX2LOZQEu&hR=B&AL%U`5XJuTx2uoe$UbC?@i(>X!u2l|VeTqPqLrq)2H
zc)Q9KwTD+$-Mi)vyDu$m>z+!w*PrnXijK-4$v&Q3^1B2ZixmF*YY8ali6#%S>L%8r
z)lj>;-We%)(d{LH(}?y9Zd%hX
zw@p%7=fXynIi);Tf2IPlQ*!EqW7-_qUUZ%_nstM@*S5aMTi2H_U&{gB@Ier7U>0%A
zgQIcG(_>W)gPRNXtv*9Xf8NoE$gDP*mJ4)Tqf!=wz_A_Y)3?@vl1uLYZR03Im_L6g
z9X8re>4l9
z#iR8%6R%%>{q5?I&t7J3zvyAamhU;Yn&sfXc$Z-6zvUMh`y0`HAQKV!y_wU!h6ZYW
z-n?;eG3bE84Z5~8;DqR|sz`dIK`8xtclG)
zV{wym4~U(mSkJp$BdmOKorzXEa8?mt+e(|O17<&3m)ZOE#On$_7Q%8&cyU!X@iB6c
zq+E{Jq2O1-#osVW2Z0+DEr+2#C?
z4})d6F64&(F&S4@gC5kDWlN+~GxcsYV8QPHf
z2l@isxQ$$zYOhldRH1&mCkKdq6Ug5@^Jk7SLj>n)0aM?$09jVDBYfaN9$d$TuN$`%
zx%@rJ;0>7mdvn`@55$~#JvV`*h6iOsI>^$K&H{c(O-~!;tA!;#HTT>4n3nQgE-@+Z
zNqgFuCk_bA#AWE8K5r;mdH`LO5>+k83=jj9V)YkZ;>P{f_#B_CFla`n17;^H>)!
zrVEYzfs=6kcj@^bIuVpKy*mH%hvDxylC?9mj#2paMt#Y|b?K}iH@TfTLsnV2JSs49
z_HCPDbk#c0tyd+40Qb{osrWE6|Ubt2Ph!-xk>6ERMs8lQLaiI>$x(1
z1fq2ghMl!=ye}R?Y_`P1&U&uh%g-2KbzJ^1Cj-Px^|_Jorbcf6iC30uIZ~o0W$@*KF@By-DDQVzN1e7vxwj
zBH2qCg7=cR$HK*cfAQ_doT0iCT_R)3>|ycyvukA7I6BVe3Hj?SU5y4F
z>Qk)Cf}@h2(MMi$d*;Dey=}_^5<(il<_AASJyoWA)+Z3yXX|M(Adp#kkhO1(-KZZP
zS@OHe?jIWjt4y?q%gfwTXwtWjbxwCb^nUeVYgcj~4)JK|;B
za#>X6kZFcrc!!WL41IJ#KC!28FeW04l=>+hg);r@K>iKKvKYcduM8G?IxX&vj%;O`
z_45e{UrvXB%Fa&gcXET{tM;M_Fmt#DMF|ufbdyi=NrR
z6Emqn$MYx3**uev`s%^jIrbZ$wC=ygWLpJ(1SCwAbdtrHi$}Yz-_6fd9PG;{Aj(m9
z;vmE*_K|cVRc2QVD!`>*cFT(;tM~fPmY;!w=eNhNl*`N@pP}C0lYO{d$AANBGM0*l
z+w|YHl329s2TMyh0|8td*rWN9U2*vs8y9`#17`HIC2->f7KF89*EVg;T8H{hsZ9%p
zv!65!r>0;y$Sx<~r0z>LB2LE}mV+xZWhAdLe{hS#C7wxHSl`&)rm*6W=F9g$4C|-Z
z{J`oHZItCM{o6Z#xxHEcH2UMGzC(Uex`PkB#*V;3m<2s;i+AWGiTx5
zleJE}Fdj_M^9!vTOPiBdPjmy^Sg=0xJw53VijVZ~;-=667&>%~)mFO|(lj4(nB^(D
z%J90cB=XCFJG)d3p+hh<7`kTr=f;;Mdz3tDJ9$M4Rkg}4W*vjDU4i3%c8E+JwN4x=
zwCR^3<6kXBc2^8QT-?hXBfi6&(M~An_P<17%-EZqK>)~PtYRrYgPz5r>Gu~erEb71
zC#z!?3Be#4DGIwe?|+wn6_z*?S#vR3j?RKpZ9p%&gK9hlO8WObsz`;=;3?e|7Wb
zXuV`p4PwKU@{32NM(@6u7ToQrZ`qXDO@^RQ_fomQyRq%qn^9cr$G%Y+U$w`G9QuKV
zHQOe4%d=#;aiLxdc93{K7+Pmvh2k50Obe)OK|=pa?ej$9B!8rE4tg!ZvQA*%9_G1s0!7DhXv?nH
z8kPt3MR@g&6Yrky{C?+<3rkCxZ_^q?#XrXkjf8im8UOm-E9JhL#?B(?(~DDu^`8be
zJATD;w`Y$@x~-EBmDQc)>WBvWA<_s+Kn26vC69Zolq~!&frpF4y?94_Ft&YFcYnlHO{A}0BbLN+SC{*hqv0iz(684_
zKZdYCckg;Nx~}}$m(F;>oAoQe@Y%%m-o@GpyQ)qd%gya08}t+i>H@}6QFYtg;!_mM
zi>^c@UPR7}uq=hO5?{TUhmR6J4uWPXB5!RtpDM%JFR|+Q^}T->t88^8Xxq{Qd|Zbp
z6rS_uzo=4)2zkooLK>?we(j&KOKBP0?5!*WSz3U5Mm(BdO6TC?JflI-P%3hww~vIE
zbd?QV83|{}*75w6)ie(&Ok4rSKF3+})Ia+=61;
z(aunl@-ghq$E>8`$H$04DzOEzgymu8*619|Z_ro%8C}Z%0B8FH_?fBIzR!LO-yz^&8((1YmRt9-u
wR0$f*f~UWGa&b1qT;2aK9?tN;K2
diff --git a/client/ui/netbird-systemtray-update-disconnected.png b/client/ui/netbird-systemtray-update-disconnected.png
index 44a30dc9a773cdbf9ba60b3dd2cc0b849aeb2d0f..3fbe88953aac6d630a053a45c5d8a51ea2a0e4e3 100644
GIT binary patch
delta 8580
zcmZ8mWmr_-*S!oifbq)|FW
zV1OCsKmNYF&-31|_w0T4U3ZhJnW7xxc35l?g#t8%}u|d>tUh_Q;=K>4=W)Z}BZ##;oiQV+l^A1?#&k@?A@P!I|f63hK3K(l=LV
zm$W?xOecW{G_GPQ!jzHc`~(kzhdD$o?eKBD0|suEL@HxR>AAE*cLER-)e4!xt7j9dzyXvIle;G)TS2x3rDHvM`pPMZ%
zgbNO8`JA-xv)^fZUaka1xV{
zads7yk`|K|l#rH?5|nXqk`R;;k#%%=B!+wG?3yl0SjHnJE+r-{DlRE4BPlB@#ZJn~
z%BrrZYjDrZ#n0cv=g9+hF(FYAA#tg6ETM_K%so*F6H(E7qB8d+#rQ?U?um%}XA$iE
z^q#KIBM;XQ6~_P#66M!zq&D%=ol}(kl5y~h^(x$tCOU-ptFRytDuC8h^wHZ
zn3I&Cqln`pX;*PcT&U9Ncc2m;A-qA5)RJ)U`g3iK9K%H>#?`fqxIq#ooD$n=|2=*zquLVNxhFMr-^=ACY3Hq
zveZYEBLhvDj_*3p$CNO0raTJ&yWe$Z^O*vIFDrI>@%*gqJbWqHe>4;?T~x>*eVm#I
z9AqVkgWt^w|7o97Vsoy?cJvvN&D&JKRpcovw!W_E$w3Ie8->}ocIByv9e-X!h$b$c
z(N(Ivp~De$;uNT!G7m
zt%U-6n*&fvwkHokG_@ZD2dJ(eEsH}~i|amD2PO&r<GoM1u`2Q-Y<)T5Y{iD
z81L%kx{UJ>z4e?mOs(FPC~kRuIp()&v)|8Bzgsd)u0^rm$P3O&fRyo$u65gMhVDZ8Lv{!p97$&sQz-J&YYv@%9wX~~taooU
z`yKw)$bRYegZsdy0o{KL+6^!P$0OI?dC8W;zyB5ttsYeE
z!|(|~=KlG}Tx(~;FYd^EH?41_UW=~!8yX~J`7EJjV!R%F!>7Nl4-FZtMTIAeQX{+e
zYrjz=CB_1MzK`n~#Se|Qf1z>hEDiGK6`X`%}wTjr|5kOH8XN
z=`Ib|**PL%LF?_CE_(gd)a|
z1M!;*x~mK2V;>F*kslY+tU+UBIvj>*t#xE-!3W;eo<{SFgZvr!TgIB3Wk$CX7{}%w
z{1&8!Ail%Z+c>deX+_k2Q+^ZPEYRaB#gHOI)=Njr@2=WsUZB@5W$Rb8oqI=x`3pNDqs=pSnx
z*bQBe{o{7-c)v1?8Xb_J=u#XDeruyrplv?+U4NgD3>^Ag^PB#fNc@r9QIvXC?Kd({
zhB(;^Xth|<*~a{Zw)&aR&oD2aEA6i@5U$J3-=*ErL+`Ss>^ao|bN0yQ5g(a(XVq3n
z=YUIm^|^p;eOfGnVB>o7Uid%>e_5Om0E--Eh|=!N`Yd)cZ{$JRFARP*c*bu9@Ic{n1C;?t3aMdpZruPI)-&QYm+sH{*tUsvZ{+#^^gdC
z-8^!r{ae&c$B*%Ygb?`54iyjF7#NZn)TxSfX>_Y1SyCAEP*|o$0|oEityG%}XNW2JAX46spZGS6VQk
z$V_dm5sOG81Xc=;-%ny3rT?@|o?Hdkl1kEgvbi)cX;S_T0%NFbnbz=uqb7ODBtr=e
zwg$*$t`c+2t+m$Wp6oMzYaBR39Lrhi=RXeqoTx^Z;1RAq{`%aUKfE75g6n-CG79C9
zqHB8Agy_&vwC9)DAT9QxwbISx%XgO-wnh7$qn
zcQw52?urjvQ~p#%q1gm>RnvdgAO0hx!Hy%@<#!{mk_9RIR1&13MehrC*R?j%MK@uh
z9ZXy~LC%XCMmZ+IFNCY7LJ|+>Ca)jUSXUi-fH=yVEM>i-i>@ojKp21ocw2@w>J>ld
z*77h~nRnmi&EKZ7>%kRY>(G|heDiuEYjSr&yxiUq01k2+Dg8#n;}I0B7*iCP6+uRmT!xE++0dBz-*lE&WhAp
zFv;`zv2$Hcf`-{gj&f%e#a;YW+y{1lP|w6R}gix=P`$
zLo^Oe-0iPVI0y2hk;1IwBn%3wuVi%acytLU1rOi?K~2m=FkCx8B-DKtJC+M
z=o+uL96ta1GEP};(C`5RjlQx2g+xu_P6UhVL)-B!c<<<4Ql&@uYShpyGPltl|8+`C
zDQBuj)z_g*FB#7t$veaj&NpEjfQUCqJ6U8pmAh^Nqe^pi<=E7DL$QX2^Uo=_m}6^R
zvJXS23e7t^Pe>R5X}Vn>m`
z1&6B3eZg{5=qr!o9ze_1IH^Q)K`@pGN84I?MfBYzsF3Q5{^Fm|5MP3%$rYD=f#QNk=-&~WiMx>5$xH(ln;&L$O
zrcBK;u5SN`L=itG)b_PO+zCo#{oav~s(4fA?%|r#W?yi}3r<5h7o{wn*#(=kXNwdV
zTi_MV+x4u&7a(QL!8W?vzr{B6<3|DqMFQsi+pfZRV6Q1EJyWIq!*#21dRvRYdTqf|
z1gc4KZLCBH##G$y#j}>&`s@l9a+_=S%h1dRfgGI??(`wBmUwIab|R7d&sq1oratA5
z@jVKLr&Ey#AB$2C@?BPXCHu9>VDGrR1ymMkyxH?rNeFWbQA_HArvd*2}hU
zI1bvXPC?NpF1PsG#k8D8ex!!uQ9ms`VXDQdzjU4-8!hwS9!f0LKMmZ%2fBJw5Gt^Z
z)z%0Q?9pG!vpnWSrUbw{30|a(*{Yq#JxFDl(y|=X$wLPZk>5|+Z7G`Qu4H(9t-Lx~
zbVb8>)jL3_Z7%Rno@ztEgJrt?TwV{&_e?uMZyeEBy>-fr8+R!PY?&o4Q)^+7knjiL
zsbd$jIyvLMhqRUSnT}}bN!RGH!6DIeZfnThl@89$ViC_1QHwWdjs{OT)GkTaUOs^Ye|(ubK{}tx&{ILuzmG
zgni0*SERFZUTs2sWE1=KK^DkCj;ZzF$jJZ~LH*#}GPeBgqGKtt%ylX(xNpRm>^)Y%fO+#7_rA6|R<0Qhr
zZETF4&5~ppRZ#kMxhm!b`=VRP{;}Ikss3_jz(r%1GPr@^;&50ktOT_HxRN>BSVN)C
z&V_pC5@H3jD}70UiTvr|{Gw#SV6RHCZ+}A1$(*fJLg<-Yya|v`k~4LV`C+efe#%vI
zqE91~8*40yQ)|sVzJP5fA)|E$C4l!zuB%faQ9*NVYn-@J52%S(RBEOknAp&MzrPU9
zUtqUg>tGOOr}=snr0lo8Wl-Oj|Bp~QO6J8f^I?`ha;xP#u^+XzmX{sy8jX;9H$=YT
zaSeAL5(P_B281B%wUPBUtD7+#-)HoelSI!vOu`DK(p7P_#y^?XNanr$bwsbwud|k@
zaNxAe!`|PU=+hr7xDOMzXB_teLeyDGCkJ$n-*s!y}>75q=SwJ
z&l>ojM5_q_4gEuyA<)_u-LM=ZaB=$xwOg(hx^VgyS+;!ly&3EAw8q%&jG4PYkf7~E
z)%(DE&kpY#H!Z(;G@coer_OkY*>s64DU4r2MRtoON3gR^!28=j>f#Zl`=1^T{4nVC
zr@MUqPMoH6xdGoGx&-HW
zN?^sJ?VsM4Cm(@yHww6(U>T3P1N=ufFSe2GA6%EOFvd|cf&e}{mAZLxS3#FX&=q;x
zxW{Hz*OG%{+fYBYPwiyX6Jye&s#uf*8P{hfS6vIPI)CF?=y#npmf}mF2qL;w6iXaY
zW~(3R;1#N@L2L!xcMRq(%=4uy0ApdtVX;H#Fad?h8z+Cv<$Y*My0MnL
zCuCkd9(AN-FLZS9iN=TwK;W`1bZA|!XNwOOm{)5^;9y@bym*v;?wZ?t04_?tv<@LI
zs#E*Yo8e;n?9z!8$-_<+U|w9X{A}R5bEjP5K=^W_BByIkh9fs)A#~?r`-_k!1s8Wm
z?wTR+qW)i#6ZM#9a(0UN(63`xC05YZUJ$`apA+!FOpoIHbPMWHuu|Qm1m1YI1|E+Z!SEK}
z^sS)P+B*!WH)DA{*=U7tADW^UV@K}e?{yf=&C!~{L~2Lt7>mFM0HK2b^?$t?JJos9
zvV+%vyk(O*C}0wo7=j!QXCp-xQ?qo03G~jeCTE)uv}Qr5(xAUa&z1PSdoS#}_E?0@
zKJ7j(TXyZ1m9{7iJmUS#Lgj>XUFc?gsc!@*QQ>ivVTe+->SKTS7VLI-IR-7)
zDp;ZrzB4Ccs@~0(l(l)Pct^aDz5u#}%We5}QC}^b{Ja{oCADJ{>QOk7i!J0II*JR^
zPu~F`mZWg~1FQCTAp+F#B_p%hvldX44TO2S;SCYloao8NRr7mlg07RTNf3i~(AU%2
zioD*vP0I#x6bP5Uy9wTiW#DJYc5{By{+%nFI0k&8KcM^13&DRc}
z4FqYj&NF+$X-VI;IJ-K&eEMy9`5llksAygsc{|4U{$e>}#$*HseEh3Pqxc*1m6IzB
zgRa0H{p+@#x*{Nh7lQvY-r4iy4U#H&KqslkfygRRK|zqk@I8?6<$-n<;>YIpVSncm
zIQ_~qCorT^bWb?b)Oc-M1Xn<&H*sFy1?5V+j(TmUjDSQ+9oeivV-IwniiWxZ-nW`!
z;BHjsPb&ml)38Oe%2{reFMH90+NF$JawOFqIz?X~6bn>up$34~qklsQ>&h$QYi~KJ
zo!lEgkJ)0VJ;&1K$2s(<=S2B)MN5E;c--hksgjLojw+pln-0=o-7nbD
z^wpR9gN8q2k#2C=}bDbw`
zX_dIkDNgI@%z#(?Y5hd7JW4+9Cx_50iPulnv)CaeH$moYW1hTtf=eENGX>XfIN9H7
zfkF|y1xUGvh04>`4(gtW-|7VY;@1_{VX;m2Ytk^FaPQ~$%l@P8!flZKXasSQC##B>
z>lwp8?H8@`%FDXl8Ep0%`q~Y>Tc(DEr>S>mE6oHaLst0z`n+}KKVLFh%FD4J<6iIe
zTN(~5zX-n2PZt6d9Yef%*&W^;Q@(ayi8YBWGyuXJ$S_*6hhuu9{p=7(w*B8%*1e8E
zJ59}OFuRBm+o8qieQ+I-rbUPl*1A;f;ogrZ{C7{9QM$OP{==yzsJSm*ZrorgADy
zUJw~qTKV$Czs9W|hruSLOxmS9VG;Duey6F>gPk>CRn$NpWoj_66vDO;=R+h2DPscR
zkQE9L2JiyT1_w=rn(VA^E;GiMj~xsK4*tLoZAEtHlei#d8|cU>`%KaKf&ME6^oA_VSAzH
zgl&(}t2j8>-e|^7nhs80Qihk}g}>FuK`BIO5teqIU0bnjgX|=J5sQVvS3#EG7%6*S
z17q{(Z@sxigbuldr@+|avwdD@0AUCPaVX@>osQ*__`aEtRj)Vp~W1&_DaBkJ2Z1)e-LTb)2SZ;MqauQ%}yf){V-ota6qdcM_)eiXek}oWB
z>pr;BZ%=i?34~9>N0v6juiv!)j08Dt;9nnd5gL9q?oO#6}xj
z4#sj5I^4zeXmlKqZ7)Uk6>)Nl#n}rHr5)<0do<&J4qp=y%_qgJU63tdYt^odXUK5b
zxNEJbnJv3UDPjt~`Q-B%40auqEseb#?Y22T^Pi!k#uaj9%OFhUX}Ful)O(0+bk=pm
z-!aqMmo1~34{vKk0XN3~o7wznesOB~eVww}vaxiFX9d^rjk%OvFIOC7h5S&GJKo4u
zGM>wqw{6dSL@V%>mmyGNAJj%@A$I#Gud$jG30I$0+3o
zJ1n-}3h&$oE99Xsf3BlO$<18i6@N`6BIR0D!<_cAOzO||J
z|M~-$C2;#_{d6q~(!!h7DDzWE_;JC3vlzZ@?C_6=?s(SdjxGfKk))NW(UY_Zeypi5
z&?mn*5J1i2cB{T*+P15lz`9rbVJ*eo;*}(+Lw!6DelZoH8hR}DoCJzIyF~~ig`%8I
zGzK+0qW|XBjzO0%nZ``FcX$hq5N%_@oAjxRsE>OxP6Pr$d-_`_jA=P?_1_m!fXv+O;My(puczy@=EyA<~h@t7PV84_i~b@I|=s3>03n;A5tmu|8Mf
zaWt!qoP0j-IVS(PihvqLb6F^=?!+1ADq5Q%Z59jk{@CEBc)asCEQro8#6)_6*}#}`
z0yLMNGrur#@$j)4rDh|4ZB4@O;XCa?#Uh35RHCHVPUdxDzOdVhX|q?vxvT|;eWkC$
z`xcFreqF}24xP^ffXv4`@ZJ{WvlBx`tR4=G-*SVbWohLY^h$Z9f$Rss3hZquJ_>pI
zT*Qll&=E~12z>r2<@52chXR7#z{{M?`a@f-!qpxb3{N=`(VGC8k|;{jU-$Ruo3njA
z$;@g}bgzKcaZv@#-6=XYuLAZKzh5Rk#wA_#XMXf%kI@HAkh=Y_Yeh}tXmeF~>=%b|
z19vjMy;^s?xyA4I2EOk^fJ@>f#>*y@UsvE7^6mcPy4
zKpcjr!gXAWiZw*p9XuZFpC;Y{O_HEhqk(pmHH-qWHrV^v7Uj^R>ZfFOFxW
z3#VZ6V~c=3R7)WrI+5D)sH#YKc_IiTPkTA$Sh
zs%*nuye}7WB`b&Jw+uT;Y3vE*$US+&gkST4rYeADNPF^js-ms2tzjnt
From 049b5fb7ede553da0d812590d083b6c77e5ca4a2 Mon Sep 17 00:00:00 2001
From: pascal-fischer <32096965+pascal-fischer@users.noreply.github.com>
Date: Mon, 19 Aug 2024 12:50:11 +0200
Subject: [PATCH 77/77] Split DB calls in peer login (#2439)
---
management/server/account.go | 22 +++++++
management/server/file_store.go | 29 +++++++++
management/server/peer.go | 110 +++++++++++++++++---------------
management/server/sql_store.go | 28 ++++++++
management/server/store.go | 2 +
5 files changed, 141 insertions(+), 50 deletions(-)
diff --git a/management/server/account.go b/management/server/account.go
index 97227274620..4c150fd7ee5 100644
--- a/management/server/account.go
+++ b/management/server/account.go
@@ -2072,6 +2072,28 @@ func (am *DefaultAccountManager) GetAccountIDForPeerKey(ctx context.Context, pee
return am.Store.GetAccountIDByPeerPubKey(ctx, peerKey)
}
+func (am *DefaultAccountManager) handleUserPeer(ctx context.Context, peer *nbpeer.Peer, settings *Settings) (bool, error) {
+ user, err := am.Store.GetUserByUserID(ctx, peer.UserID)
+ if err != nil {
+ return false, err
+ }
+
+ err = checkIfPeerOwnerIsBlocked(peer, user)
+ if err != nil {
+ return false, err
+ }
+
+ if peerLoginExpired(ctx, peer, settings) {
+ err = am.handleExpiredPeer(ctx, user, peer)
+ if err != nil {
+ return false, err
+ }
+ return true, nil
+ }
+
+ return false, nil
+}
+
// addAllGroup to account object if it doesn't exist
func addAllGroup(account *Account) error {
if len(account.Groups) == 0 {
diff --git a/management/server/file_store.go b/management/server/file_store.go
index 6e3536bcdf6..1927568ef18 100644
--- a/management/server/file_store.go
+++ b/management/server/file_store.go
@@ -469,6 +469,35 @@ func (s *FileStore) GetUserByTokenID(_ context.Context, tokenID string) (*User,
return account.Users[userID].Copy(), nil
}
+func (s *FileStore) GetUserByUserID(_ context.Context, userID string) (*User, error) {
+ accountID, ok := s.UserID2AccountID[userID]
+ if !ok {
+ return nil, status.Errorf(status.NotFound, "accountID not found: provided userID doesn't exists")
+ }
+
+ account, err := s.getAccount(accountID)
+ if err != nil {
+ return nil, err
+ }
+
+ return account.Users[userID].Copy(), nil
+}
+
+func (s *FileStore) GetAccountGroups(ctx context.Context, accountID string) ([]*nbgroup.Group, error) {
+ account, err := s.getAccount(accountID)
+ if err != nil {
+ return nil, err
+ }
+
+ groupsSlice := make([]*nbgroup.Group, 0, len(account.Groups))
+
+ for _, group := range account.Groups {
+ groupsSlice = append(groupsSlice, group)
+ }
+
+ return groupsSlice, nil
+}
+
// GetAllAccounts returns all accounts
func (s *FileStore) GetAllAccounts(_ context.Context) (all []*Account) {
s.mux.Lock()
diff --git a/management/server/peer.go b/management/server/peer.go
index 7afe6ee0d4e..93234d9dee6 100644
--- a/management/server/peer.go
+++ b/management/server/peer.go
@@ -549,16 +549,25 @@ func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync PeerSync, ac
return nil, nil, nil, status.NewPeerNotRegisteredError()
}
- err = checkIfPeerOwnerIsBlocked(peer, account)
- if err != nil {
- return nil, nil, nil, err
+ if peer.UserID != "" {
+ log.Infof("Peer has no userID")
+
+ user, err := account.FindUser(peer.UserID)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ err = checkIfPeerOwnerIsBlocked(peer, user)
+ if err != nil {
+ return nil, nil, nil, err
+ }
}
if peerLoginExpired(ctx, peer, account.Settings) {
return nil, nil, nil, status.NewPeerLoginExpiredError()
}
- peer, updated := updatePeerMeta(peer, sync.Meta, account)
+ updated := peer.UpdateMetaIfNew(sync.Meta)
if updated {
err = am.Store.SavePeer(ctx, account.Id, peer)
if err != nil {
@@ -624,31 +633,28 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login PeerLogin)
// it means that the client has already checked if it needs login and had been through the SSO flow
// so, we can skip this check and directly proceed with the login
if login.UserID == "" {
+ log.Info("Peer needs login")
err = am.checkIFPeerNeedsLoginWithoutLock(ctx, accountID, login)
if err != nil {
return nil, nil, nil, err
}
}
- unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
+ unlockAccount := am.Store.AcquireReadLockByUID(ctx, accountID)
+ defer unlockAccount()
+ unlockPeer := am.Store.AcquireWriteLockByUID(ctx, login.WireGuardPubKey)
defer func() {
- if unlock != nil {
- unlock()
+ if unlockPeer != nil {
+ unlockPeer()
}
}()
- // fetch the account from the store once more after acquiring lock to avoid concurrent updates inconsistencies
- account, err := am.Store.GetAccount(ctx, accountID)
+ peer, err := am.Store.GetPeerByPeerPubKey(ctx, login.WireGuardPubKey)
if err != nil {
return nil, nil, nil, err
}
- peer, err := account.FindPeerByPubKey(login.WireGuardPubKey)
- if err != nil {
- return nil, nil, nil, status.NewPeerNotRegisteredError()
- }
-
- err = checkIfPeerOwnerIsBlocked(peer, account)
+ settings, err := am.Store.GetAccountSettings(ctx, accountID)
if err != nil {
return nil, nil, nil, err
}
@@ -656,21 +662,39 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login PeerLogin)
// this flag prevents unnecessary calls to the persistent store.
shouldStorePeer := false
updateRemotePeers := false
- if peerLoginExpired(ctx, peer, account.Settings) {
- err = am.handleExpiredPeer(ctx, login, account, peer)
+
+ if login.UserID != "" {
+ changed, err := am.handleUserPeer(ctx, peer, settings)
if err != nil {
return nil, nil, nil, err
}
- updateRemotePeers = true
- shouldStorePeer = true
+ if changed {
+ shouldStorePeer = true
+ updateRemotePeers = true
+ }
}
- isRequiresApproval, isStatusChanged, err := am.integratedPeerValidator.IsNotValidPeer(ctx, account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra)
+ groups, err := am.Store.GetAccountGroups(ctx, accountID)
if err != nil {
return nil, nil, nil, err
}
- peer, updated := updatePeerMeta(peer, login.Meta, account)
+ var grps []string
+ for _, group := range groups {
+ for _, id := range group.Peers {
+ if id == peer.ID {
+ grps = append(grps, group.ID)
+ break
+ }
+ }
+ }
+
+ isRequiresApproval, isStatusChanged, err := am.integratedPeerValidator.IsNotValidPeer(ctx, accountID, peer, grps, settings.Extra)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ updated := peer.UpdateMetaIfNew(login.Meta)
if updated {
shouldStorePeer = true
}
@@ -687,8 +711,13 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login PeerLogin)
}
}
- unlock()
- unlock = nil
+ unlockPeer()
+ unlockPeer = nil
+
+ account, err := am.Store.GetAccount(ctx, accountID)
+ if err != nil {
+ return nil, nil, nil, err
+ }
if updateRemotePeers || isStatusChanged {
am.updateAccountPeers(ctx, account)
@@ -746,36 +775,30 @@ func (am *DefaultAccountManager) getValidatedPeerWithMap(ctx context.Context, is
return peer, account.GetPeerNetworkMap(ctx, peer.ID, customZone, approvedPeersMap, am.metrics.AccountManagerMetrics()), postureChecks, nil
}
-func (am *DefaultAccountManager) handleExpiredPeer(ctx context.Context, login PeerLogin, account *Account, peer *nbpeer.Peer) error {
- err := checkAuth(ctx, login.UserID, peer)
+func (am *DefaultAccountManager) handleExpiredPeer(ctx context.Context, user *User, peer *nbpeer.Peer) error {
+ err := checkAuth(ctx, user.Id, peer)
if err != nil {
return err
}
// If peer was expired before and if it reached this point, it is re-authenticated.
// UserID is present, meaning that JWT validation passed successfully in the API layer.
- updatePeerLastLogin(peer, account)
-
- // sync user last login with peer last login
- user, err := account.FindUser(login.UserID)
+ peer = peer.UpdateLastLogin()
+ err = am.Store.SavePeer(ctx, peer.AccountID, peer)
if err != nil {
- return status.Errorf(status.Internal, "couldn't find user")
+ return err
}
- err = am.Store.SaveUserLastLogin(account.Id, user.Id, peer.LastLogin)
+ err = am.Store.SaveUserLastLogin(user.AccountID, user.Id, peer.LastLogin)
if err != nil {
return err
}
- am.StoreEvent(ctx, login.UserID, peer.ID, account.Id, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain()))
+ am.StoreEvent(ctx, user.Id, peer.ID, user.AccountID, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain()))
return nil
}
-func checkIfPeerOwnerIsBlocked(peer *nbpeer.Peer, account *Account) error {
+func checkIfPeerOwnerIsBlocked(peer *nbpeer.Peer, user *User) error {
if peer.AddedWithSSOLogin() {
- user, err := account.FindUser(peer.UserID)
- if err != nil {
- return status.Errorf(status.PermissionDenied, "user doesn't exist")
- }
if user.IsBlocked() {
return status.Errorf(status.PermissionDenied, "user is blocked")
}
@@ -805,11 +828,6 @@ func peerLoginExpired(ctx context.Context, peer *nbpeer.Peer, settings *Settings
return false
}
-func updatePeerLastLogin(peer *nbpeer.Peer, account *Account) {
- peer.UpdateLastLogin()
- account.UpdatePeer(peer)
-}
-
// UpdatePeerSSHKey updates peer's public SSH key
func (am *DefaultAccountManager) UpdatePeerSSHKey(ctx context.Context, peerID string, sshKey string) error {
if sshKey == "" {
@@ -908,14 +926,6 @@ func (am *DefaultAccountManager) GetPeer(ctx context.Context, accountID, peerID,
return nil, status.Errorf(status.Internal, "user %s has no access to peer %s under account %s", userID, peerID, accountID)
}
-func updatePeerMeta(peer *nbpeer.Peer, meta nbpeer.PeerSystemMeta, account *Account) (*nbpeer.Peer, bool) {
- if peer.UpdateMetaIfNew(meta) {
- account.UpdatePeer(peer)
- return peer, true
- }
- return peer, false
-}
-
// updateAccountPeers updates all peers that belong to an account.
// Should be called when changes have to be synced to peers.
func (am *DefaultAccountManager) updateAccountPeers(ctx context.Context, account *Account) {
diff --git a/management/server/sql_store.go b/management/server/sql_store.go
index c44ab7f0951..912e31410c6 100644
--- a/management/server/sql_store.go
+++ b/management/server/sql_store.go
@@ -468,6 +468,34 @@ func (s *SqlStore) GetUserByTokenID(ctx context.Context, tokenID string) (*User,
return &user, nil
}
+func (s *SqlStore) GetUserByUserID(ctx context.Context, userID string) (*User, error) {
+ var user User
+ result := s.db.First(&user, idQueryCondition, userID)
+ if result.Error != nil {
+ if errors.Is(result.Error, gorm.ErrRecordNotFound) {
+ return nil, status.Errorf(status.NotFound, "user not found: index lookup failed")
+ }
+ log.WithContext(ctx).Errorf("error when getting user from the store: %s", result.Error)
+ return nil, status.Errorf(status.Internal, "issue getting user from store")
+ }
+
+ return &user, nil
+}
+
+func (s *SqlStore) GetAccountGroups(ctx context.Context, accountID string) ([]*nbgroup.Group, error) {
+ var groups []*nbgroup.Group
+ result := s.db.Find(&groups, idQueryCondition, accountID)
+ if result.Error != nil {
+ if errors.Is(result.Error, gorm.ErrRecordNotFound) {
+ return nil, status.Errorf(status.NotFound, "accountID not found: index lookup failed")
+ }
+ log.WithContext(ctx).Errorf("error when getting groups from the store: %s", result.Error)
+ return nil, status.Errorf(status.Internal, "issue getting groups from store")
+ }
+
+ return groups, nil
+}
+
func (s *SqlStore) GetAllAccounts(ctx context.Context) (all []*Account) {
var accounts []Account
result := s.db.Find(&accounts)
diff --git a/management/server/store.go b/management/server/store.go
index 864871c8e53..a2b4893917e 100644
--- a/management/server/store.go
+++ b/management/server/store.go
@@ -41,6 +41,8 @@ type Store interface {
GetAccountByPrivateDomain(ctx context.Context, domain string) (*Account, error)
GetTokenIDByHashedToken(ctx context.Context, secret string) (string, error)
GetUserByTokenID(ctx context.Context, tokenID string) (*User, error)
+ GetUserByUserID(ctx context.Context, userID string) (*User, error)
+ GetAccountGroups(ctx context.Context, accountID string) ([]*nbgroup.Group, error)
GetPostureCheckByChecksDefinition(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error)
SaveAccount(ctx context.Context, account *Account) error
SaveUsers(accountID string, users map[string]*User) error