From 896599aa57028e8a62495244800068392caac0f6 Mon Sep 17 00:00:00 2001 From: Zoltan Papp Date: Wed, 6 Mar 2024 11:38:08 +0100 Subject: [PATCH] Implement API response cache (#1645) Apply peer validator cache mechanism --------- Co-authored-by: Maycon Santos Co-authored-by: Yury Gargay Co-authored-by: Viktor Liu Co-authored-by: Bethuel Mmbaga Co-authored-by: pascal-fischer <32096965+pascal-fischer@users.noreply.github.com> Co-authored-by: Misha Bragin --- .github/ISSUE_TEMPLATE/bug-issue-report.md | 2 +- .../workflows/test-infrastructure-files.yml | 9 +- .golangci.yaml | 9 + .goreleaser_ui.yaml | 4 +- README.md | 31 +- client/cmd/testutil.go | 5 +- client/internal/auth/oauth.go | 2 +- client/internal/dns/file_linux.go | 27 +- client/internal/dns/file_parser_linux.go | 63 ++ client/internal/dns/file_parser_linux_test.go | 130 +++ client/internal/dns/host_linux.go | 2 +- client/internal/dns/resolvconf_linux.go | 4 +- client/internal/engine_test.go | 14 +- client/ui/client_ui.go | 92 +- client/ui/netbird-systemtray-connected.ico | Bin 4452 -> 5139 bytes client/ui/netbird-systemtray-connected.png | Bin 7251 -> 9105 bytes client/ui/netbird-systemtray-default.ico | Bin 2876 -> 0 bytes client/ui/netbird-systemtray-default.png | Bin 4938 -> 0 bytes client/ui/netbird-systemtray-disconnected.ico | Bin 0 -> 5167 bytes client/ui/netbird-systemtray-disconnected.png | Bin 0 -> 9258 bytes .../netbird-systemtray-update-connected.ico | Bin 0 -> 7678 bytes .../netbird-systemtray-update-connected.png | Bin 0 -> 11471 bytes ...netbird-systemtray-update-disconnected.ico | Bin 0 -> 7966 bytes ...netbird-systemtray-update-disconnected.png | Bin 0 -> 11929 bytes client/ui/netbird-systemtray-update.ico | Bin 4726 -> 0 bytes client/ui/netbird-systemtray-update.png | Bin 7521 -> 0 bytes go.mod | 2 +- go.sum | 4 +- iface/netstack/tun.go | 2 +- infrastructure_files/download-geolite2.sh | 11 +- .../getting-started-with-zitadel.sh | 26 +- infrastructure_files/management.json.tmpl | 7 + infrastructure_files/nginx.tmpl.conf | 1 + management/client/client_test.go | 18 +- management/client/grpc.go | 10 +- management/cmd/management.go | 11 +- management/proto/management.pb.go | 1000 +++++++++-------- management/proto/management.proto | 9 + management/server/account.go | 20 +- management/server/account_test.go | 13 +- management/server/activity/event.go | 2 +- management/server/geolocation/database.go | 210 ++++ management/server/geolocation/geolocation.go | 32 +- management/server/geolocation/store.go | 31 +- management/server/geolocation/utils.go | 176 +++ management/server/grpcserver.go | 4 + management/server/http/api/openapi.yml | 34 +- management/server/http/api/types.gen.go | 40 +- .../http/middleware/auth_middleware_test.go | 5 +- .../server/http/middleware/bypass/bypass.go | 43 +- .../http/middleware/bypass/bypass_test.go | 30 +- .../server/http/posture_checks_handler.go | 36 +- .../http/posture_checks_handler_test.go | 69 +- management/server/idp/auth0.go | 76 +- .../server/integrated_approval/interface.go | 3 +- management/server/management_test.go | 8 +- management/server/mock_server/account_mock.go | 12 +- management/server/peer.go | 20 +- management/server/peer/peer.go | 20 +- management/server/posture/checks.go | 32 +- management/server/posture/checks_test.go | 2 +- management/server/posture/network.go | 16 +- management/server/posture/network_test.go | 28 +- management/server/scheduler.go | 59 +- management/server/scheduler_test.go | 20 +- management/server/user.go | 19 +- management/server/user_test.go | 3 +- signal/client/grpc.go | 7 +- signal/cmd/run.go | 6 +- 69 files changed, 1799 insertions(+), 772 deletions(-) delete mode 100644 client/ui/netbird-systemtray-default.ico delete mode 100644 client/ui/netbird-systemtray-default.png create mode 100644 client/ui/netbird-systemtray-disconnected.ico create mode 100644 client/ui/netbird-systemtray-disconnected.png create mode 100644 client/ui/netbird-systemtray-update-connected.ico create mode 100644 client/ui/netbird-systemtray-update-connected.png create mode 100644 client/ui/netbird-systemtray-update-disconnected.ico create mode 100644 client/ui/netbird-systemtray-update-disconnected.png delete mode 100644 client/ui/netbird-systemtray-update.ico delete mode 100644 client/ui/netbird-systemtray-update.png create mode 100644 management/server/geolocation/database.go create mode 100644 management/server/geolocation/utils.go diff --git a/.github/ISSUE_TEMPLATE/bug-issue-report.md b/.github/ISSUE_TEMPLATE/bug-issue-report.md index 1c63eeaee..0aa6cd77e 100644 --- a/.github/ISSUE_TEMPLATE/bug-issue-report.md +++ b/.github/ISSUE_TEMPLATE/bug-issue-report.md @@ -2,7 +2,7 @@ name: Bug/Issue report about: Create a report to help us improve title: '' -labels: ['triage'] +labels: ['triage-needed'] assignees: '' --- diff --git a/.github/workflows/test-infrastructure-files.yml b/.github/workflows/test-infrastructure-files.yml index e1261dabc..ee9739e09 100644 --- a/.github/workflows/test-infrastructure-files.yml +++ b/.github/workflows/test-infrastructure-files.yml @@ -162,6 +162,13 @@ jobs: test $count -eq 4 working-directory: infrastructure_files/artifacts + - name: test geolocation databases + working-directory: infrastructure_files/artifacts + run: | + sleep 30 + docker compose exec management ls -l /var/lib/netbird/ | grep -i GeoLite2-City.mmdb + docker compose exec management ls -l /var/lib/netbird/ | grep -i geonames.db + test-getting-started-script: runs-on: ubuntu-latest steps: @@ -199,6 +206,6 @@ jobs: - name: test script run: bash -x infrastructure_files/download-geolite2.sh - name: test mmdb file exists - run: ls -l GeoLite2-City_*/GeoLite2-City.mmdb + run: test -f GeoLite2-City.mmdb - name: test geonames file exists run: test -f geonames.db diff --git a/.golangci.yaml b/.golangci.yaml index 757a60a39..3c5f4d5b8 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -63,6 +63,14 @@ linters-settings: enable: - nilness + revive: + rules: + - name: exported + severity: warning + disabled: false + arguments: + - "checkPrivateReceivers" + - "sayRepetitiveInsteadOfStutters" tenv: # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. @@ -93,6 +101,7 @@ linters: - nilerr # finds the code that returns nil even if it checks that the error is not nil - nilnil # checks that there is no simultaneous return of nil error and an invalid value - predeclared # predeclared finds code that shadows one of Go's predeclared identifiers + - revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed - thelper # thelper detects Go test helpers without t.Helper() call and checks the consistency of test helpers. - wastedassign # wastedassign finds wasted assignment statements diff --git a/.goreleaser_ui.yaml b/.goreleaser_ui.yaml index 66a22ee34..b13085e86 100644 --- a/.goreleaser_ui.yaml +++ b/.goreleaser_ui.yaml @@ -54,7 +54,7 @@ nfpms: contents: - src: client/ui/netbird.desktop dst: /usr/share/applications/netbird.desktop - - src: client/ui/netbird-systemtray-default.png + - src: client/ui/netbird-systemtray-connected.png dst: /usr/share/pixmaps/netbird.png dependencies: - netbird @@ -71,7 +71,7 @@ nfpms: contents: - src: client/ui/netbird.desktop dst: /usr/share/applications/netbird.desktop - - src: client/ui/netbird-systemtray-default.png + - src: client/ui/netbird-systemtray-connected.png dst: /usr/share/pixmaps/netbird.png dependencies: - netbird diff --git a/README.md b/README.md index 192377f06..0fa0a7dd2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- :hatching_chick: New Release! Self-hosting in under 5 min. - + :hatching_chick: New Release! Device Posture Checks. + Learn more

@@ -42,25 +42,22 @@ **Secure.** NetBird enables secure remote access by applying granular access policies, while allowing you to manage them intuitively from a single place. Works universally on any infrastructure. -### Secure peer-to-peer VPN with SSO and MFA in minutes +### Open-Source Network Security in a Single Platform -https://user-images.githubusercontent.com/700848/197345890-2e2cded5-7b7a-436f-a444-94e80dd24f46.mov +![download (2)](https://github.com/netbirdio/netbird/assets/700848/16210ac2-7265-44c1-8d4e-8fae85534dac) ### Key features -| Connectivity | Management | Automation | Platforms | -|---------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|----------------------------------------------------------------------------|---------------------------------------| -|
  • - \[x] Kernel WireGuard
|
  • - \[x] [Admin Web UI](https://github.com/netbirdio/dashboard)
|
  • - \[x] [Public API](https://docs.netbird.io/api)
|
  • - \[x] Linux
| -|
  • - \[x] Peer-to-peer connections
|
  • - \[x] Auto peer discovery and configuration
|
  • - \[x] [Setup keys for bulk network provisioning](https://docs.netbird.io/how-to/register-machines-using-setup-keys)
|
  • - \[x] Mac
| -|
  • - \[x] Peer-to-peer encryption
|
  • - \[x] [IdP integrations](https://docs.netbird.io/selfhosted/identity-providers)
|
  • - \[x] [Self-hosting quickstart script](https://docs.netbird.io/selfhosted/selfhosted-quickstart)
|
  • - \[x] Windows
| -|
  • - \[x] Connection relay fallback
|
  • - \[x] [SSO & MFA support](https://docs.netbird.io/how-to/installation#running-net-bird-with-sso-login)
|
  • - \[x] IdP groups sync with JWT
|
  • - \[x] Android
| -|
  • - \[x] [Routes to external networks](https://docs.netbird.io/how-to/routing-traffic-to-private-networks)
|
  • - \[x] [Access control - groups & rules](https://docs.netbird.io/how-to/manage-network-access)
| |
  • - \[x] iOS
| -|
  • - \[x] NAT traversal with BPF
|
  • - \[x] [Private DNS](https://docs.netbird.io/how-to/manage-dns-in-your-network)
| |
  • - \[x] Docker
| -|
  • - \[x] Post-quantum-secure connection through [Rosenpass](https://rosenpass.eu)
|
  • - \[x] [Multiuser support](https://docs.netbird.io/how-to/add-users-to-your-network)
| |
  • - \[x] OpenWRT
| -| |
  • - \[x] [Activity logging](https://docs.netbird.io/how-to/monitor-system-and-network-activity)
| | | -| |
  • - \[x] SSH access management
| | | - - +| Connectivity | Management | Security | Automation | Platforms | +|------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------| +|
  • - \[x] Kernel WireGuard
|
  • - \[x] [Admin Web UI](https://github.com/netbirdio/dashboard)
|
  • - \[x] [SSO & MFA support](https://docs.netbird.io/how-to/installation#running-net-bird-with-sso-login)
|
  • - \[x] [Public API](https://docs.netbird.io/api)
|
  • - \[x] Linux
| +|
  • - \[x] Peer-to-peer connections
|
  • - \[x] Auto peer discovery and configuration
|
  • - \[x] [Access control - groups & rules](https://docs.netbird.io/how-to/manage-network-access)
|
  • - \[x] [Setup keys for bulk network provisioning](https://docs.netbird.io/how-to/register-machines-using-setup-keys)
|
  • - \[x] Mac
| +|
  • - \[x] Connection relay fallback
|
  • - \[x] [IdP integrations](https://docs.netbird.io/selfhosted/identity-providers)
|
  • - \[x] [Activity logging](https://docs.netbird.io/how-to/monitor-system-and-network-activity)
|
  • - \[x] [Self-hosting quickstart script](https://docs.netbird.io/selfhosted/selfhosted-quickstart)
|
  • - \[x] Windows
| +|
  • - \[x] [Routes to external networks](https://docs.netbird.io/how-to/routing-traffic-to-private-networks)
|
  • - \[x] [Private DNS](https://docs.netbird.io/how-to/manage-dns-in-your-network)
|
  • - \[x] [Device posture checks](https://docs.netbird.io/how-to/manage-posture-checks)
|
  • - \[x] IdP groups sync with JWT
|
  • - \[x] Android
| +|
  • - \[x] NAT traversal with BPF
|
  • - \[x] [Multiuser support](https://docs.netbird.io/how-to/add-users-to-your-network)
|
  • - \[x] Peer-to-peer encryption
| |
  • - \[x] iOS
| +| | |
  • - \[x] [Quantum-resistance with Rosenpass](https://netbird.io/knowledge-hub/the-first-quantum-resistant-mesh-vpn)
| |
  • - \[x] OpenWRT
| +| | |
  • - \[x] [Periodic re-authentication](https://docs.netbird.io/how-to/enforce-periodic-user-authentication)
  • | |
    • - \[x] [Serverless](https://docs.netbird.io/how-to/netbird-on-faas)
    | +| | | | |
    • - \[x] Docker
    | ### Quickstart with NetBird Cloud - Download and install NetBird at [https://app.netbird.io/install](https://app.netbird.io/install) diff --git a/client/cmd/testutil.go b/client/cmd/testutil.go index 40fd86f1d..13a434c7d 100644 --- a/client/cmd/testutil.go +++ b/client/cmd/testutil.go @@ -76,10 +76,7 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste peersUpdateManager := mgmt.NewPeersUpdateManager(nil) eventStore := &activity.InMemoryEventStore{} - if err != nil { - return nil, nil - } - iv := integrations.NewIntegratedApproval() + iv, _ := integrations.NewIntegratedApproval(eventStore) accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false, iv) if err != nil { t.Fatal(err) diff --git a/client/internal/auth/oauth.go b/client/internal/auth/oauth.go index 23bde2be2..7467584a3 100644 --- a/client/internal/auth/oauth.go +++ b/client/internal/auth/oauth.go @@ -26,7 +26,7 @@ type HTTPClient interface { } // AuthFlowInfo holds information for the OAuth 2.0 authorization flow -type AuthFlowInfo struct { +type AuthFlowInfo struct { //nolint:revive DeviceCode string `json:"device_code"` UserCode string `json:"user_code"` VerificationURI string `json:"verification_uri"` diff --git a/client/internal/dns/file_linux.go b/client/internal/dns/file_linux.go index c62da7016..b427a30e1 100644 --- a/client/internal/dns/file_linux.go +++ b/client/internal/dns/file_linux.go @@ -8,6 +8,7 @@ import ( "net/netip" "os" "strings" + "time" log "github.com/sirupsen/logrus" ) @@ -23,10 +24,16 @@ const ( fileMaxNumberOfSearchDomains = 6 ) +const ( + dnsFailoverTimeout = 4 * time.Second + dnsFailoverAttempts = 1 +) + type fileConfigurator struct { repair *repair - originalPerms os.FileMode + originalPerms os.FileMode + nbNameserverIP string } func newFileConfigurator() (hostManager, error) { @@ -64,7 +71,7 @@ func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig) error { } nbSearchDomains := searchDomains(config) - nbNameserverIP := config.ServerIP + f.nbNameserverIP = config.ServerIP resolvConf, err := parseBackupResolvConf() if err != nil { @@ -73,11 +80,11 @@ func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig) error { f.repair.stopWatchFileChanges() - err = f.updateConfig(nbSearchDomains, nbNameserverIP, resolvConf) + err = f.updateConfig(nbSearchDomains, f.nbNameserverIP, resolvConf) if err != nil { return err } - f.repair.watchFileChanges(nbSearchDomains, nbNameserverIP) + f.repair.watchFileChanges(nbSearchDomains, f.nbNameserverIP) return nil } @@ -85,10 +92,11 @@ func (f *fileConfigurator) updateConfig(nbSearchDomains []string, nbNameserverIP searchDomainList := mergeSearchDomains(nbSearchDomains, cfg.searchDomains) nameServers := generateNsList(nbNameserverIP, cfg) + options := prepareOptionsWithTimeout(cfg.others, int(dnsFailoverTimeout.Seconds()), dnsFailoverAttempts) buf := prepareResolvConfContent( searchDomainList, nameServers, - cfg.others) + options) log.Debugf("creating managed file %s", defaultResolvConfPath) err := os.WriteFile(defaultResolvConfPath, buf.Bytes(), f.originalPerms) @@ -131,7 +139,12 @@ func (f *fileConfigurator) backup() error { } func (f *fileConfigurator) restore() error { - err := copyFile(fileDefaultResolvConfBackupLocation, defaultResolvConfPath) + err := removeFirstNbNameserver(fileDefaultResolvConfBackupLocation, f.nbNameserverIP) + if err != nil { + log.Errorf("Failed to remove netbird nameserver from %s on backup restore: %s", fileDefaultResolvConfBackupLocation, err) + } + + err = copyFile(fileDefaultResolvConfBackupLocation, defaultResolvConfPath) if err != nil { return fmt.Errorf("restoring %s from %s: %w", defaultResolvConfPath, fileDefaultResolvConfBackupLocation, err) } @@ -157,7 +170,7 @@ func (f *fileConfigurator) restoreUncleanShutdownDNS(storedDNSAddress *netip.Add currentDNSAddress, err := netip.ParseAddr(resolvConf.nameServers[0]) // not a valid first nameserver -> restore if err != nil { - log.Errorf("restoring unclean shutdown: parse dns address %s failed: %s", resolvConf.nameServers[1], err) + log.Errorf("restoring unclean shutdown: parse dns address %s failed: %s", resolvConf.nameServers[0], err) return restoreResolvConfFile() } diff --git a/client/internal/dns/file_parser_linux.go b/client/internal/dns/file_parser_linux.go index 95e1ddb54..02f6d03a5 100644 --- a/client/internal/dns/file_parser_linux.go +++ b/client/internal/dns/file_parser_linux.go @@ -5,6 +5,7 @@ package dns import ( "fmt" "os" + "regexp" "strings" log "github.com/sirupsen/logrus" @@ -14,6 +15,9 @@ const ( defaultResolvConfPath = "/etc/resolv.conf" ) +var timeoutRegex = regexp.MustCompile(`timeout:\d+`) +var attemptsRegex = regexp.MustCompile(`attempts:\d+`) + type resolvConf struct { nameServers []string searchDomains []string @@ -103,3 +107,62 @@ func parseResolvConfFile(resolvConfFile string) (*resolvConf, error) { } return rconf, nil } + +// prepareOptionsWithTimeout appends timeout to existing options if it doesn't exist, +// otherwise it adds a new option with timeout and attempts. +func prepareOptionsWithTimeout(input []string, timeout int, attempts int) []string { + configs := make([]string, len(input)) + copy(configs, input) + + for i, config := range configs { + if strings.HasPrefix(config, "options") { + config = strings.ReplaceAll(config, "rotate", "") + config = strings.Join(strings.Fields(config), " ") + + if strings.Contains(config, "timeout:") { + config = timeoutRegex.ReplaceAllString(config, fmt.Sprintf("timeout:%d", timeout)) + } else { + config = strings.Replace(config, "options ", fmt.Sprintf("options timeout:%d ", timeout), 1) + } + + if strings.Contains(config, "attempts:") { + config = attemptsRegex.ReplaceAllString(config, fmt.Sprintf("attempts:%d", attempts)) + } else { + config = strings.Replace(config, "options ", fmt.Sprintf("options attempts:%d ", attempts), 1) + } + + configs[i] = config + return configs + } + } + + return append(configs, fmt.Sprintf("options timeout:%d attempts:%d", timeout, attempts)) +} + +// removeFirstNbNameserver removes the given nameserver from the given file if it is in the first position +// and writes the file back to the original location +func removeFirstNbNameserver(filename, nameserverIP string) error { + resolvConf, err := parseResolvConfFile(filename) + if err != nil { + return fmt.Errorf("parse backup resolv.conf: %w", err) + } + content, err := os.ReadFile(filename) + if err != nil { + return fmt.Errorf("read %s: %w", filename, err) + } + + if len(resolvConf.nameServers) > 1 && resolvConf.nameServers[0] == nameserverIP { + newContent := strings.Replace(string(content), fmt.Sprintf("nameserver %s\n", nameserverIP), "", 1) + + stat, err := os.Stat(filename) + if err != nil { + return fmt.Errorf("stat %s: %w", filename, err) + } + if err := os.WriteFile(filename, []byte(newContent), stat.Mode()); err != nil { + return fmt.Errorf("write %s: %w", filename, err) + } + + } + + return nil +} diff --git a/client/internal/dns/file_parser_linux_test.go b/client/internal/dns/file_parser_linux_test.go index 180ad2f9d..4263d4063 100644 --- a/client/internal/dns/file_parser_linux_test.go +++ b/client/internal/dns/file_parser_linux_test.go @@ -6,6 +6,8 @@ import ( "os" "path/filepath" "testing" + + "github.com/stretchr/testify/assert" ) func Test_parseResolvConf(t *testing.T) { @@ -172,3 +174,131 @@ nameserver 192.168.0.1 t.Errorf("unexpected resolv.conf content: %v", cfg) } } + +func TestPrepareOptionsWithTimeout(t *testing.T) { + tests := []struct { + name string + others []string + timeout int + attempts int + expected []string + }{ + { + name: "Append new options with timeout and attempts", + others: []string{"some config"}, + timeout: 2, + attempts: 2, + expected: []string{"some config", "options timeout:2 attempts:2"}, + }, + { + name: "Modify existing options to exclude rotate and include timeout and attempts", + others: []string{"some config", "options rotate someother"}, + timeout: 3, + attempts: 2, + expected: []string{"some config", "options attempts:2 timeout:3 someother"}, + }, + { + name: "Existing options with timeout and attempts are updated", + others: []string{"some config", "options timeout:4 attempts:3"}, + timeout: 5, + attempts: 4, + expected: []string{"some config", "options timeout:5 attempts:4"}, + }, + { + name: "Modify existing options, add missing attempts before timeout", + others: []string{"some config", "options timeout:4"}, + timeout: 4, + attempts: 3, + expected: []string{"some config", "options attempts:3 timeout:4"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := prepareOptionsWithTimeout(tc.others, tc.timeout, tc.attempts) + assert.Equal(t, tc.expected, result) + }) + } +} + +func TestRemoveFirstNbNameserver(t *testing.T) { + testCases := []struct { + name string + content string + ipToRemove string + expected string + }{ + { + name: "Unrelated nameservers with comments and options", + content: `# This is a comment +options rotate +nameserver 1.1.1.1 +# Another comment +nameserver 8.8.4.4 +search example.com`, + ipToRemove: "9.9.9.9", + expected: `# This is a comment +options rotate +nameserver 1.1.1.1 +# Another comment +nameserver 8.8.4.4 +search example.com`, + }, + { + name: "First nameserver matches", + content: `search example.com +nameserver 9.9.9.9 +# oof, a comment +nameserver 8.8.4.4 +options attempts:5`, + ipToRemove: "9.9.9.9", + expected: `search example.com +# oof, a comment +nameserver 8.8.4.4 +options attempts:5`, + }, + { + name: "Target IP not the first nameserver", + // nolint:dupword + content: `# Comment about the first nameserver +nameserver 8.8.4.4 +# Comment before our target +nameserver 9.9.9.9 +options timeout:2`, + ipToRemove: "9.9.9.9", + // nolint:dupword + expected: `# Comment about the first nameserver +nameserver 8.8.4.4 +# Comment before our target +nameserver 9.9.9.9 +options timeout:2`, + }, + { + name: "Only nameserver matches", + content: `options debug +nameserver 9.9.9.9 +search localdomain`, + ipToRemove: "9.9.9.9", + expected: `options debug +nameserver 9.9.9.9 +search localdomain`, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tempDir := t.TempDir() + tempFile := filepath.Join(tempDir, "resolv.conf") + err := os.WriteFile(tempFile, []byte(tc.content), 0644) + assert.NoError(t, err) + + err = removeFirstNbNameserver(tempFile, tc.ipToRemove) + assert.NoError(t, err) + + content, err := os.ReadFile(tempFile) + assert.NoError(t, err) + + assert.Equal(t, tc.expected, string(content), "The resulting content should match the expected output.") + }) + } +} diff --git a/client/internal/dns/host_linux.go b/client/internal/dns/host_linux.go index 37d8f704a..cb246bcfe 100644 --- a/client/internal/dns/host_linux.go +++ b/client/internal/dns/host_linux.go @@ -65,7 +65,7 @@ func newHostManager(wgInterface string) (hostManager, error) { return nil, err } - log.Debugf("discovered mode is: %s", osManager) + log.Infof("System DNS manager discovered: %s", osManager) return newHostManagerFromType(wgInterface, osManager) } diff --git a/client/internal/dns/resolvconf_linux.go b/client/internal/dns/resolvconf_linux.go index b8f753e28..72db5faf1 100644 --- a/client/internal/dns/resolvconf_linux.go +++ b/client/internal/dns/resolvconf_linux.go @@ -53,10 +53,12 @@ func (r *resolvconf) applyDNSConfig(config HostDNSConfig) error { searchDomainList := searchDomains(config) searchDomainList = mergeSearchDomains(searchDomainList, r.originalSearchDomains) + options := prepareOptionsWithTimeout(r.othersConfigs, int(dnsFailoverTimeout.Seconds()), dnsFailoverAttempts) + buf := prepareResolvConfContent( searchDomainList, append([]string{config.ServerIP}, r.originalNameServers...), - r.othersConfigs) + options) // create a backup for unclean shutdown detection before the resolv.conf is changed if err := createUncleanShutdownIndicator(defaultResolvConfPath, resolvConfManager, config.ServerIP); err != nil { diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index ffcb8fabb..7b8509cc5 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -71,10 +71,10 @@ func TestEngine_SSH(t *testing.T) { defer cancel() engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{ - WgIfaceName: "utun101", - WgAddr: "100.64.0.1/24", - WgPrivateKey: key, - WgPort: 33100, + WgIfaceName: "utun101", + WgAddr: "100.64.0.1/24", + WgPrivateKey: key, + WgPort: 33100, ServerSSHAllowed: true, }, MobileDependency{}, peer.NewRecorder("https://mgm")) @@ -1048,10 +1048,8 @@ func startManagement(dataDir string) (*grpc.Server, string, error) { peersUpdateManager := server.NewPeersUpdateManager(nil) eventStore := &activity.InMemoryEventStore{} - if err != nil { - return nil, "", err - } - accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false, integrations.NewIntegratedApproval()) + ia, _ := integrations.NewIntegratedApproval(eventStore) + accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false, ia) if err != nil { return nil, "", err } diff --git a/client/ui/client_ui.go b/client/ui/client_ui.go index d90156f56..e242a26db 100644 --- a/client/ui/client_ui.go +++ b/client/ui/client_ui.go @@ -61,7 +61,7 @@ func main() { flag.Parse() - a := app.New() + a := app.NewWithID("NetBird") a.SetIcon(fyne.NewStaticResource("netbird", iconDisconnectedPNG)) client := newServiceClient(daemonAddr, a, showSettings) @@ -82,17 +82,23 @@ var iconConnectedICO []byte //go:embed netbird-systemtray-connected.png var iconConnectedPNG []byte -//go:embed netbird-systemtray-default.ico +//go:embed netbird-systemtray-disconnected.ico var iconDisconnectedICO []byte -//go:embed netbird-systemtray-default.png +//go:embed netbird-systemtray-disconnected.png var iconDisconnectedPNG []byte -//go:embed netbird-systemtray-update.ico -var iconUpdateICO []byte +//go:embed netbird-systemtray-update-disconnected.ico +var iconUpdateDisconnectedICO []byte -//go:embed netbird-systemtray-update.png -var iconUpdatePNG []byte +//go:embed netbird-systemtray-update-disconnected.png +var iconUpdateDisconnectedPNG []byte + +//go:embed netbird-systemtray-update-connected.ico +var iconUpdateConnectedICO []byte + +//go:embed netbird-systemtray-update-connected.png +var iconUpdateConnectedPNG []byte //go:embed netbird-systemtray-update-cloud.ico var iconUpdateCloudICO []byte @@ -105,10 +111,11 @@ type serviceClient struct { addr string conn proto.DaemonServiceClient - icConnected []byte - icDisconnected []byte - icUpdate []byte - icUpdateCloud []byte + icConnected []byte + icDisconnected []byte + icUpdateConnected []byte + icUpdateDisconnected []byte + icUpdateCloud []byte // systray menu items mStatus *systray.MenuItem @@ -123,9 +130,10 @@ type serviceClient struct { mQuit *systray.MenuItem // application with main windows. - app fyne.App - wSettings fyne.Window - showSettings bool + app fyne.App + wSettings fyne.Window + showSettings bool + sendNotification bool // input elements for settings form iMngURL *widget.Entry @@ -139,6 +147,7 @@ type serviceClient struct { preSharedKey string adminURL string + connected bool update *version.Update daemonVersion string updateIndicationLock sync.Mutex @@ -150,9 +159,10 @@ type serviceClient struct { // This constructor also builds the UI elements for the settings window. func newServiceClient(addr string, a fyne.App, showSettings bool) *serviceClient { s := &serviceClient{ - ctx: context.Background(), - addr: addr, - app: a, + ctx: context.Background(), + addr: addr, + app: a, + sendNotification: false, showSettings: showSettings, update: version.NewUpdate(), @@ -161,13 +171,15 @@ func newServiceClient(addr string, a fyne.App, showSettings bool) *serviceClient if runtime.GOOS == "windows" { s.icConnected = iconConnectedICO s.icDisconnected = iconDisconnectedICO - s.icUpdate = iconUpdateICO + s.icUpdateConnected = iconUpdateConnectedICO + s.icUpdateDisconnected = iconUpdateDisconnectedICO s.icUpdateCloud = iconUpdateCloudICO } else { s.icConnected = iconConnectedPNG s.icDisconnected = iconDisconnectedPNG - s.icUpdate = iconUpdatePNG + s.icUpdateConnected = iconUpdateConnectedPNG + s.icUpdateDisconnected = iconUpdateDisconnectedPNG s.icUpdateCloud = iconUpdateCloudPNG } @@ -367,9 +379,18 @@ func (s *serviceClient) updateStatus() error { s.updateIndicationLock.Lock() defer s.updateIndicationLock.Unlock() + // notify the user when the session has expired + if status.Status == string(internal.StatusNeedsLogin) { + s.onSessionExpire() + } + var systrayIconState bool if status.Status == string(internal.StatusConnected) && !s.mUp.Disabled() { - if !s.isUpdateIconActive { + s.connected = true + s.sendNotification = true + if s.isUpdateIconActive { + systray.SetIcon(s.icUpdateConnected) + } else { systray.SetIcon(s.icConnected) } systray.SetTooltip("NetBird (Connected)") @@ -378,7 +399,10 @@ func (s *serviceClient) updateStatus() error { s.mDown.Enable() systrayIconState = true } else if status.Status != string(internal.StatusConnected) && s.mUp.Disabled() { - if !s.isUpdateIconActive { + s.connected = false + if s.isUpdateIconActive { + systray.SetIcon(s.icUpdateDisconnected) + } else { systray.SetIcon(s.icDisconnected) } systray.SetTooltip("NetBird (Disconnected)") @@ -605,10 +629,30 @@ func (s *serviceClient) onUpdateAvailable() { defer s.updateIndicationLock.Unlock() s.mUpdate.Show() - s.mAbout.SetIcon(s.icUpdateCloud) - s.isUpdateIconActive = true - systray.SetIcon(s.icUpdate) + + if s.connected { + systray.SetIcon(s.icUpdateConnected) + } else { + systray.SetIcon(s.icUpdateDisconnected) + } +} + +// onSessionExpire sends a notification to the user when the session expires. +func (s *serviceClient) onSessionExpire() { + if s.sendNotification { + title := "Connection session expired" + if runtime.GOOS == "darwin" { + title = "NetBird connection session expired" + } + s.app.SendNotification( + fyne.NewNotification( + title, + "Please re-authenticate to connect to the network", + ), + ) + s.sendNotification = false + } } func openURL(url string) error { diff --git a/client/ui/netbird-systemtray-connected.ico b/client/ui/netbird-systemtray-connected.ico index 621afce9fb2a65c34e68df372aaa6770ee98c8d0..80550aa37e20d9d134cda6c0cfdeb731f1765cfe 100644 GIT binary patch literal 5139 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 I7T)py1qRz*Pyhe` diff --git a/client/ui/netbird-systemtray-connected.png b/client/ui/netbird-systemtray-connected.png index c5878d0187e9a5151814a6dfb29e311e475110b4..f4d156da87ab794a49d48359f653e24a673d69cd 100644 GIT binary patch literal 9105 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 literal 7251 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 diff --git a/client/ui/netbird-systemtray-default.ico b/client/ui/netbird-systemtray-default.ico deleted file mode 100644 index 5a025267599c8bb00849160cc4da5dbdb4ea41ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2876 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 diff --git a/client/ui/netbird-systemtray-default.png b/client/ui/netbird-systemtray-default.png deleted file mode 100644 index 12e7a2dc1008861fe1d98914b8fb44364990c7aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4938 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 diff --git a/client/ui/netbird-systemtray-disconnected.ico b/client/ui/netbird-systemtray-disconnected.ico new file mode 100644 index 0000000000000000000000000000000000000000..aa75268b0c7c110352eed1b76b21a97e8e24b909 GIT binary patch literal 5167 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 My6&w?HM_9?0TFN?z5oCK literal 0 HcmV?d00001 diff --git a/client/ui/netbird-systemtray-update-connected.png b/client/ui/netbird-systemtray-update-connected.png new file mode 100644 index 0000000000000000000000000000000000000000..a0c4533406c399aacba6184ad7e74be9e20a0cf0 GIT binary patch literal 11471 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 literal 0 HcmV?d00001 diff --git a/client/ui/netbird-systemtray-update-disconnected.png b/client/ui/netbird-systemtray-update-disconnected.png new file mode 100644 index 0000000000000000000000000000000000000000..44a30dc9a773cdbf9ba60b3dd2cc0b849aeb2d0f GIT binary patch literal 11929 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 E0BadFW&i*H literal 0 HcmV?d00001 diff --git a/client/ui/netbird-systemtray-update.ico b/client/ui/netbird-systemtray-update.ico deleted file mode 100644 index 1a1c4086d5ec7ed97f8c633b4ade786fe24fb522..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4726 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 diff --git a/client/ui/netbird-systemtray-update.png b/client/ui/netbird-systemtray-update.png deleted file mode 100644 index 1f4651df9db198901f4498d240c498c504731f40..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7521 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 diff --git a/go.mod b/go.mod index 79862cab2..c69dd248b 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/nadoo/ipset v0.5.0 github.com/netbirdio/management-integrations/additions v0.0.0-20240226151841-2e4fe2407450 - github.com/netbirdio/management-integrations/integrations v0.0.0-20240226151841-2e4fe2407450 + github.com/netbirdio/management-integrations/integrations v0.0.0-20240305130559-469a80446ac7 github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/oschwald/maxminddb-golang v1.12.0 github.com/patrickmn/go-cache v2.1.0+incompatible diff --git a/go.sum b/go.sum index 15c8db563..c2d32a0c1 100644 --- a/go.sum +++ b/go.sum @@ -379,8 +379,8 @@ github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= github.com/netbirdio/management-integrations/additions v0.0.0-20240226151841-2e4fe2407450 h1:qA4S5YFt6/s0kQ8wKLjq8faLxuBSte1WzjWfmQmyJTU= github.com/netbirdio/management-integrations/additions v0.0.0-20240226151841-2e4fe2407450/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= -github.com/netbirdio/management-integrations/integrations v0.0.0-20240226151841-2e4fe2407450 h1:jEepZRVo60IN+us4E8BvNUbasoViFwqJ7exKix4aQyc= -github.com/netbirdio/management-integrations/integrations v0.0.0-20240226151841-2e4fe2407450/go.mod h1:B0nMS3es77gOvPYhc0K91fAzTkQLi/jRq5TffUN3klM= +github.com/netbirdio/management-integrations/integrations v0.0.0-20240305130559-469a80446ac7 h1:YYIQJbRhANmNFClkCmjBa0w33RpTzsF2DpbGAWhul6Y= +github.com/netbirdio/management-integrations/integrations v0.0.0-20240305130559-469a80446ac7/go.mod h1:B0nMS3es77gOvPYhc0K91fAzTkQLi/jRq5TffUN3klM= 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= diff --git a/iface/netstack/tun.go b/iface/netstack/tun.go index 8c7c3a8ff..c180e4ef5 100644 --- a/iface/netstack/tun.go +++ b/iface/netstack/tun.go @@ -8,7 +8,7 @@ import ( "golang.zx2c4.com/wireguard/tun/netstack" ) -type NetStackTun struct { +type NetStackTun struct { //nolint:revive address string mtu int listenAddress string diff --git a/infrastructure_files/download-geolite2.sh b/infrastructure_files/download-geolite2.sh index e09873627..4a9db5e01 100755 --- a/infrastructure_files/download-geolite2.sh +++ b/infrastructure_files/download-geolite2.sh @@ -43,21 +43,18 @@ download_geolite_mmdb() { mkdir -p "$EXTRACTION_DIR" tar -xzvf "$DATABASE_FILE" > /dev/null 2>&1 - # Create a SHA256 signature file MMDB_FILE="GeoLite2-City.mmdb" - cd "$EXTRACTION_DIR" - sha256sum "$MMDB_FILE" > "$MMDB_FILE.sha256" - echo "SHA256 signature created for $MMDB_FILE." - cd - > /dev/null 2>&1 + cp "$EXTRACTION_DIR"/"$MMDB_FILE" $MMDB_FILE # Remove downloaded files + rm -r "$EXTRACTION_DIR" rm "$DATABASE_FILE" "$SIGNATURE_FILE" # Done. Print next steps echo "" echo "Process completed successfully." - echo "Now you can place $EXTRACTION_DIR/$MMDB_FILE to 'datadir' of management service." - echo -e "Example:\n\tdocker compose cp $EXTRACTION_DIR/$MMDB_FILE management:/var/lib/netbird/" + echo "Now you can place $MMDB_FILE to 'datadir' of management service." + echo -e "Example:\n\tdocker compose cp $MMDB_FILE management:/var/lib/netbird/" } diff --git a/infrastructure_files/getting-started-with-zitadel.sh b/infrastructure_files/getting-started-with-zitadel.sh index 24cb108e1..29f0e4606 100644 --- a/infrastructure_files/getting-started-with-zitadel.sh +++ b/infrastructure_files/getting-started-with-zitadel.sh @@ -137,6 +137,13 @@ create_new_application() { BASE_REDIRECT_URL2=$5 LOGOUT_URL=$6 ZITADEL_DEV_MODE=$7 + DEVICE_CODE=$8 + + if [[ $DEVICE_CODE == "true" ]]; then + GRANT_TYPES='["OIDC_GRANT_TYPE_AUTHORIZATION_CODE","OIDC_GRANT_TYPE_DEVICE_CODE","OIDC_GRANT_TYPE_REFRESH_TOKEN"]' + else + GRANT_TYPES='["OIDC_GRANT_TYPE_AUTHORIZATION_CODE","OIDC_GRANT_TYPE_REFRESH_TOKEN"]' + fi RESPONSE=$( curl -sS -X POST "$INSTANCE_URL/management/v1/projects/$PROJECT_ID/apps/oidc" \ @@ -154,10 +161,7 @@ create_new_application() { "RESPONSETypes": [ "OIDC_RESPONSE_TYPE_CODE" ], - "grantTypes": [ - "OIDC_GRANT_TYPE_AUTHORIZATION_CODE", - "OIDC_GRANT_TYPE_REFRESH_TOKEN" - ], + "grantTypes": '"$GRANT_TYPES"', "appType": "OIDC_APP_TYPE_USER_AGENT", "authMethodType": "OIDC_AUTH_METHOD_TYPE_NONE", "version": "OIDC_VERSION_1_0", @@ -340,10 +344,10 @@ init_zitadel() { # create zitadel spa applications echo "Creating new Zitadel SPA Dashboard application" - DASHBOARD_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Dashboard" "$BASE_REDIRECT_URL/nb-auth" "$BASE_REDIRECT_URL/nb-silent-auth" "$BASE_REDIRECT_URL/" "$ZITADEL_DEV_MODE") + DASHBOARD_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Dashboard" "$BASE_REDIRECT_URL/nb-auth" "$BASE_REDIRECT_URL/nb-silent-auth" "$BASE_REDIRECT_URL/" "$ZITADEL_DEV_MODE" "false") echo "Creating new Zitadel SPA Cli application" - CLI_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Cli" "http://localhost:53000/" "http://localhost:54000/" "http://localhost:53000/" "true") + CLI_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Cli" "http://localhost:53000/" "http://localhost:54000/" "http://localhost:53000/" "true" "true") MACHINE_USER_ID=$(create_service_user "$INSTANCE_URL" "$PAT") @@ -561,6 +565,8 @@ renderCaddyfile() { reverse_proxy /.well-known/openid-configuration h2c://zitadel:8080 reverse_proxy /openapi/* h2c://zitadel:8080 reverse_proxy /debug/* h2c://zitadel:8080 + reverse_proxy /device/* h2c://zitadel:8080 + reverse_proxy /device h2c://zitadel:8080 # Dashboard reverse_proxy /* dashboard:80 } @@ -629,6 +635,14 @@ renderManagementJson() { "ManagementEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/management/v1" } }, + "DeviceAuthorizationFlow": { + "Provider": "hosted", + "ProviderConfig": { + "Audience": "$NETBIRD_AUTH_CLIENT_ID_CLI", + "ClientID": "$NETBIRD_AUTH_CLIENT_ID_CLI", + "Scope": "openid" + } + }, "PKCEAuthorizationFlow": { "ProviderConfig": { "Audience": "$NETBIRD_AUTH_CLIENT_ID_CLI", diff --git a/infrastructure_files/management.json.tmpl b/infrastructure_files/management.json.tmpl index 0b607245f..eadb90ce2 100644 --- a/infrastructure_files/management.json.tmpl +++ b/infrastructure_files/management.json.tmpl @@ -26,6 +26,13 @@ "Username": "", "Password": null }, + "ReverseProxy": { + "TrustedHTTPProxies": [], + "TrustedHTTPProxiesCount": 0, + "TrustedPeers": [ + "0.0.0.0/0" + ] + }, "Datadir": "", "DataStoreEncryptionKey": "$NETBIRD_DATASTORE_ENC_KEY", "StoreConfig": { diff --git a/infrastructure_files/nginx.tmpl.conf b/infrastructure_files/nginx.tmpl.conf index 77739a67a..23fd760aa 100644 --- a/infrastructure_files/nginx.tmpl.conf +++ b/infrastructure_files/nginx.tmpl.conf @@ -46,6 +46,7 @@ server { proxy_set_header X-Scheme $scheme; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Host $host; + grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Proxy dashboard location / { diff --git a/management/client/client_test.go b/management/client/client_test.go index b97d7862d..5c9a8b24c 100644 --- a/management/client/client_test.go +++ b/management/client/client_test.go @@ -2,7 +2,6 @@ package client import ( "context" - "github.com/netbirdio/management-integrations/integrations" "net" "path/filepath" "sync" @@ -16,6 +15,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" + "github.com/netbirdio/management-integrations/integrations" "github.com/netbirdio/netbird/encryption" mgmtProto "github.com/netbirdio/netbird/management/proto" mgmt "github.com/netbirdio/netbird/management/server" @@ -61,7 +61,8 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) { peersUpdateManager := mgmt.NewPeersUpdateManager(nil) eventStore := &activity.InMemoryEventStore{} - accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false, integrations.NewIntegratedApproval()) + ia, _ := integrations.NewIntegratedApproval(eventStore) + accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false, ia) if err != nil { t.Fatal(err) } @@ -364,10 +365,11 @@ func Test_SystemMetaDataFromClient(t *testing.T) { WiretrusteeVersion: info.WiretrusteeVersion, KernelVersion: info.KernelVersion, - NetworkAddresses: protoNetAddr, - SysSerialNumber: info.SystemSerialNumber, - SysProductName: info.SystemProductName, - SysManufacturer: info.SystemManufacturer, + NetworkAddresses: protoNetAddr, + SysSerialNumber: info.SystemSerialNumber, + SysProductName: info.SystemProductName, + SysManufacturer: info.SystemManufacturer, + Environment: &mgmtProto.Environment{Cloud: info.Environment.Cloud, Platform: info.Environment.Platform}, } assert.Equal(t, ValidKey, actualValidKey) @@ -408,7 +410,9 @@ func isEqual(a, b *mgmtProto.PeerSystemMeta) bool { a.GetUiVersion() == b.GetUiVersion() && a.GetSysSerialNumber() == b.GetSysSerialNumber() && a.GetSysProductName() == b.GetSysProductName() && - a.GetSysManufacturer() == b.GetSysManufacturer() + a.GetSysManufacturer() == b.GetSysManufacturer() && + a.GetEnvironment().Cloud == b.GetEnvironment().Cloud && + a.GetEnvironment().Platform == b.GetEnvironment().Platform } func Test_GetDeviceAuthorizationFlow(t *testing.T) { diff --git a/management/client/grpc.go b/management/client/grpc.go index 3c4106aef..0234f866c 100644 --- a/management/client/grpc.go +++ b/management/client/grpc.go @@ -26,6 +26,8 @@ import ( "github.com/netbirdio/netbird/management/proto" ) +const ConnectTimeout = 10 * time.Second + // ConnStateNotifier is a wrapper interface of the status recorders type ConnStateNotifier interface { MarkManagementDisconnected(error) @@ -49,7 +51,7 @@ func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsE transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})) } - mgmCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + mgmCtx, cancel := context.WithTimeout(ctx, ConnectTimeout) defer cancel() conn, err := grpc.DialContext( mgmCtx, @@ -318,7 +320,7 @@ func (c *GrpcClient) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*pro log.Errorf("failed to encrypt message: %s", err) return nil, err } - mgmCtx, cancel := context.WithTimeout(c.ctx, 5*time.Second) + mgmCtx, cancel := context.WithTimeout(c.ctx, ConnectTimeout) defer cancel() resp, err := c.realClient.Login(mgmCtx, &proto.EncryptedMessage{ WgPubKey: c.key.PublicKey().String(), @@ -474,5 +476,9 @@ func infoToMetaData(info *system.Info) *proto.PeerSystemMeta { SysSerialNumber: info.SystemSerialNumber, SysManufacturer: info.SystemManufacturer, SysProductName: info.SystemProductName, + Environment: &proto.Environment{ + Cloud: info.Environment.Cloud, + Platform: info.Environment.Platform, + }, } } diff --git a/management/cmd/management.go b/management/cmd/management.go index cea8dfae9..d2f91829e 100644 --- a/management/cmd/management.go +++ b/management/cmd/management.go @@ -43,6 +43,7 @@ import ( "github.com/netbirdio/netbird/management/server/metrics" "github.com/netbirdio/netbird/management/server/telemetry" "github.com/netbirdio/netbird/util" + "github.com/netbirdio/netbird/version" ) // ManagementLegacyPort is the port that was used before by the Management gRPC server. @@ -166,13 +167,15 @@ var ( geo, err := geolocation.NewGeolocation(config.Datadir) if err != nil { - log.Warnf("could not initialize geo location service, we proceed without geo support") + log.Warnf("could not initialize geo location service: %v, we proceed without geo support", err) } else { log.Infof("geo location service has been initialized from %s", config.Datadir) } - integratedPeerApproval := integrations.NewIntegratedApproval() - + integratedPeerApproval, err := integrations.NewIntegratedApproval(eventStore) + if err != nil { + return fmt.Errorf("failed to initialize integrated peer approval: %v", err) + } accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain, dnsDomain, eventStore, geo, userDeleteFromIDPEnabled, integratedPeerApproval) if err != nil { @@ -317,12 +320,14 @@ var ( } } + log.Infof("management server version %s", version.NetbirdVersion()) log.Infof("running HTTP server and gRPC server on the same port: %s", listener.Addr().String()) serveGRPCWithHTTP(listener, rootHandler, tlsEnabled) SetupCloseHandler() <-stopCh + integratedPeerApproval.Stop() if geo != nil { _ = geo.Stop() } diff --git a/management/proto/management.pb.go b/management/proto/management.pb.go index aa5122909..18077ea89 100644 --- a/management/proto/management.pb.go +++ b/management/proto/management.pb.go @@ -73,7 +73,7 @@ func (x HostConfig_Protocol) Number() protoreflect.EnumNumber { // Deprecated: Use HostConfig_Protocol.Descriptor instead. func (HostConfig_Protocol) EnumDescriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{10, 0} + return file_management_proto_rawDescGZIP(), []int{11, 0} } type DeviceAuthorizationFlowProvider int32 @@ -116,7 +116,7 @@ func (x DeviceAuthorizationFlowProvider) Number() protoreflect.EnumNumber { // Deprecated: Use DeviceAuthorizationFlowProvider.Descriptor instead. func (DeviceAuthorizationFlowProvider) EnumDescriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{17, 0} + return file_management_proto_rawDescGZIP(), []int{18, 0} } type FirewallRuleDirection int32 @@ -162,7 +162,7 @@ func (x FirewallRuleDirection) Number() protoreflect.EnumNumber { // Deprecated: Use FirewallRuleDirection.Descriptor instead. func (FirewallRuleDirection) EnumDescriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{27, 0} + return file_management_proto_rawDescGZIP(), []int{28, 0} } type FirewallRuleAction int32 @@ -208,7 +208,7 @@ func (x FirewallRuleAction) Number() protoreflect.EnumNumber { // Deprecated: Use FirewallRuleAction.Descriptor instead. func (FirewallRuleAction) EnumDescriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{27, 1} + return file_management_proto_rawDescGZIP(), []int{28, 1} } type FirewallRuleProtocol int32 @@ -263,7 +263,7 @@ func (x FirewallRuleProtocol) Number() protoreflect.EnumNumber { // Deprecated: Use FirewallRuleProtocol.Descriptor instead. func (FirewallRuleProtocol) EnumDescriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{27, 2} + return file_management_proto_rawDescGZIP(), []int{28, 2} } type EncryptedMessage struct { @@ -589,6 +589,64 @@ func (x *PeerKeys) GetWgPubKey() []byte { return nil } +// Environment is part of the PeerSystemMeta and describes the environment the agent is running in. +type Environment struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // cloud is the cloud provider the agent is running in if applicable. + Cloud string `protobuf:"bytes,1,opt,name=cloud,proto3" json:"cloud,omitempty"` + // platform is the platform the agent is running on if applicable. + Platform string `protobuf:"bytes,2,opt,name=platform,proto3" json:"platform,omitempty"` +} + +func (x *Environment) Reset() { + *x = Environment{} + if protoimpl.UnsafeEnabled { + mi := &file_management_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Environment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Environment) ProtoMessage() {} + +func (x *Environment) ProtoReflect() protoreflect.Message { + mi := &file_management_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Environment.ProtoReflect.Descriptor instead. +func (*Environment) Descriptor() ([]byte, []int) { + return file_management_proto_rawDescGZIP(), []int{5} +} + +func (x *Environment) GetCloud() string { + if x != nil { + return x.Cloud + } + return "" +} + +func (x *Environment) GetPlatform() string { + if x != nil { + return x.Platform + } + return "" +} + // PeerSystemMeta is machine meta data like OS and version. type PeerSystemMeta struct { state protoimpl.MessageState @@ -609,12 +667,13 @@ type PeerSystemMeta struct { SysSerialNumber string `protobuf:"bytes,12,opt,name=sysSerialNumber,proto3" json:"sysSerialNumber,omitempty"` SysProductName string `protobuf:"bytes,13,opt,name=sysProductName,proto3" json:"sysProductName,omitempty"` SysManufacturer string `protobuf:"bytes,14,opt,name=sysManufacturer,proto3" json:"sysManufacturer,omitempty"` + Environment *Environment `protobuf:"bytes,15,opt,name=environment,proto3" json:"environment,omitempty"` } func (x *PeerSystemMeta) Reset() { *x = PeerSystemMeta{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[5] + mi := &file_management_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -627,7 +686,7 @@ func (x *PeerSystemMeta) String() string { func (*PeerSystemMeta) ProtoMessage() {} func (x *PeerSystemMeta) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[5] + mi := &file_management_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -640,7 +699,7 @@ func (x *PeerSystemMeta) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerSystemMeta.ProtoReflect.Descriptor instead. func (*PeerSystemMeta) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{5} + return file_management_proto_rawDescGZIP(), []int{6} } func (x *PeerSystemMeta) GetHostname() string { @@ -741,6 +800,13 @@ func (x *PeerSystemMeta) GetSysManufacturer() string { return "" } +func (x *PeerSystemMeta) GetEnvironment() *Environment { + if x != nil { + return x.Environment + } + return nil +} + type LoginResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -755,7 +821,7 @@ type LoginResponse struct { func (x *LoginResponse) Reset() { *x = LoginResponse{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[6] + mi := &file_management_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -768,7 +834,7 @@ func (x *LoginResponse) String() string { func (*LoginResponse) ProtoMessage() {} func (x *LoginResponse) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[6] + mi := &file_management_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -781,7 +847,7 @@ func (x *LoginResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead. func (*LoginResponse) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{6} + return file_management_proto_rawDescGZIP(), []int{7} } func (x *LoginResponse) GetWiretrusteeConfig() *WiretrusteeConfig { @@ -814,7 +880,7 @@ type ServerKeyResponse struct { func (x *ServerKeyResponse) Reset() { *x = ServerKeyResponse{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[7] + mi := &file_management_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -827,7 +893,7 @@ func (x *ServerKeyResponse) String() string { func (*ServerKeyResponse) ProtoMessage() {} func (x *ServerKeyResponse) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[7] + mi := &file_management_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -840,7 +906,7 @@ func (x *ServerKeyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ServerKeyResponse.ProtoReflect.Descriptor instead. func (*ServerKeyResponse) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{7} + return file_management_proto_rawDescGZIP(), []int{8} } func (x *ServerKeyResponse) GetKey() string { @@ -873,7 +939,7 @@ type Empty struct { func (x *Empty) Reset() { *x = Empty{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[8] + mi := &file_management_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -886,7 +952,7 @@ func (x *Empty) String() string { func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[8] + mi := &file_management_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -899,7 +965,7 @@ func (x *Empty) ProtoReflect() protoreflect.Message { // Deprecated: Use Empty.ProtoReflect.Descriptor instead. func (*Empty) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{8} + return file_management_proto_rawDescGZIP(), []int{9} } // WiretrusteeConfig is a common configuration of any Wiretrustee peer. It contains STUN, TURN, Signal and Management servers configurations @@ -919,7 +985,7 @@ type WiretrusteeConfig struct { func (x *WiretrusteeConfig) Reset() { *x = WiretrusteeConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[9] + mi := &file_management_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -932,7 +998,7 @@ func (x *WiretrusteeConfig) String() string { func (*WiretrusteeConfig) ProtoMessage() {} func (x *WiretrusteeConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[9] + mi := &file_management_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -945,7 +1011,7 @@ func (x *WiretrusteeConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use WiretrusteeConfig.ProtoReflect.Descriptor instead. func (*WiretrusteeConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{9} + return file_management_proto_rawDescGZIP(), []int{10} } func (x *WiretrusteeConfig) GetStuns() []*HostConfig { @@ -983,7 +1049,7 @@ type HostConfig struct { func (x *HostConfig) Reset() { *x = HostConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[10] + mi := &file_management_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -996,7 +1062,7 @@ func (x *HostConfig) String() string { func (*HostConfig) ProtoMessage() {} func (x *HostConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[10] + mi := &file_management_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1009,7 +1075,7 @@ func (x *HostConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use HostConfig.ProtoReflect.Descriptor instead. func (*HostConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{10} + return file_management_proto_rawDescGZIP(), []int{11} } func (x *HostConfig) GetUri() string { @@ -1041,7 +1107,7 @@ type ProtectedHostConfig struct { func (x *ProtectedHostConfig) Reset() { *x = ProtectedHostConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[11] + mi := &file_management_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1054,7 +1120,7 @@ func (x *ProtectedHostConfig) String() string { func (*ProtectedHostConfig) ProtoMessage() {} func (x *ProtectedHostConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[11] + mi := &file_management_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1067,7 +1133,7 @@ func (x *ProtectedHostConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtectedHostConfig.ProtoReflect.Descriptor instead. func (*ProtectedHostConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{11} + return file_management_proto_rawDescGZIP(), []int{12} } func (x *ProtectedHostConfig) GetHostConfig() *HostConfig { @@ -1111,7 +1177,7 @@ type PeerConfig struct { func (x *PeerConfig) Reset() { *x = PeerConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[12] + mi := &file_management_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1124,7 +1190,7 @@ func (x *PeerConfig) String() string { func (*PeerConfig) ProtoMessage() {} func (x *PeerConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[12] + mi := &file_management_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1137,7 +1203,7 @@ func (x *PeerConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerConfig.ProtoReflect.Descriptor instead. func (*PeerConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{12} + return file_management_proto_rawDescGZIP(), []int{13} } func (x *PeerConfig) GetAddress() string { @@ -1199,7 +1265,7 @@ type NetworkMap struct { func (x *NetworkMap) Reset() { *x = NetworkMap{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[13] + mi := &file_management_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1212,7 +1278,7 @@ func (x *NetworkMap) String() string { func (*NetworkMap) ProtoMessage() {} func (x *NetworkMap) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[13] + mi := &file_management_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1225,7 +1291,7 @@ func (x *NetworkMap) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkMap.ProtoReflect.Descriptor instead. func (*NetworkMap) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{13} + return file_management_proto_rawDescGZIP(), []int{14} } func (x *NetworkMap) GetSerial() uint64 { @@ -1311,7 +1377,7 @@ type RemotePeerConfig struct { func (x *RemotePeerConfig) Reset() { *x = RemotePeerConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[14] + mi := &file_management_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1324,7 +1390,7 @@ func (x *RemotePeerConfig) String() string { func (*RemotePeerConfig) ProtoMessage() {} func (x *RemotePeerConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[14] + mi := &file_management_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1337,7 +1403,7 @@ func (x *RemotePeerConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use RemotePeerConfig.ProtoReflect.Descriptor instead. func (*RemotePeerConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{14} + return file_management_proto_rawDescGZIP(), []int{15} } func (x *RemotePeerConfig) GetWgPubKey() string { @@ -1384,7 +1450,7 @@ type SSHConfig struct { func (x *SSHConfig) Reset() { *x = SSHConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[15] + mi := &file_management_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1397,7 +1463,7 @@ func (x *SSHConfig) String() string { func (*SSHConfig) ProtoMessage() {} func (x *SSHConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[15] + mi := &file_management_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1410,7 +1476,7 @@ func (x *SSHConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use SSHConfig.ProtoReflect.Descriptor instead. func (*SSHConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{15} + return file_management_proto_rawDescGZIP(), []int{16} } func (x *SSHConfig) GetSshEnabled() bool { @@ -1437,7 +1503,7 @@ type DeviceAuthorizationFlowRequest struct { func (x *DeviceAuthorizationFlowRequest) Reset() { *x = DeviceAuthorizationFlowRequest{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[16] + mi := &file_management_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1450,7 +1516,7 @@ func (x *DeviceAuthorizationFlowRequest) String() string { func (*DeviceAuthorizationFlowRequest) ProtoMessage() {} func (x *DeviceAuthorizationFlowRequest) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[16] + mi := &file_management_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1463,7 +1529,7 @@ func (x *DeviceAuthorizationFlowRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeviceAuthorizationFlowRequest.ProtoReflect.Descriptor instead. func (*DeviceAuthorizationFlowRequest) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{16} + return file_management_proto_rawDescGZIP(), []int{17} } // DeviceAuthorizationFlow represents Device Authorization Flow information @@ -1482,7 +1548,7 @@ type DeviceAuthorizationFlow struct { func (x *DeviceAuthorizationFlow) Reset() { *x = DeviceAuthorizationFlow{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[17] + mi := &file_management_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1495,7 +1561,7 @@ func (x *DeviceAuthorizationFlow) String() string { func (*DeviceAuthorizationFlow) ProtoMessage() {} func (x *DeviceAuthorizationFlow) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[17] + mi := &file_management_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1508,7 +1574,7 @@ func (x *DeviceAuthorizationFlow) ProtoReflect() protoreflect.Message { // Deprecated: Use DeviceAuthorizationFlow.ProtoReflect.Descriptor instead. func (*DeviceAuthorizationFlow) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{17} + return file_management_proto_rawDescGZIP(), []int{18} } func (x *DeviceAuthorizationFlow) GetProvider() DeviceAuthorizationFlowProvider { @@ -1535,7 +1601,7 @@ type PKCEAuthorizationFlowRequest struct { func (x *PKCEAuthorizationFlowRequest) Reset() { *x = PKCEAuthorizationFlowRequest{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[18] + mi := &file_management_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1548,7 +1614,7 @@ func (x *PKCEAuthorizationFlowRequest) String() string { func (*PKCEAuthorizationFlowRequest) ProtoMessage() {} func (x *PKCEAuthorizationFlowRequest) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[18] + mi := &file_management_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1561,7 +1627,7 @@ func (x *PKCEAuthorizationFlowRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PKCEAuthorizationFlowRequest.ProtoReflect.Descriptor instead. func (*PKCEAuthorizationFlowRequest) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{18} + return file_management_proto_rawDescGZIP(), []int{19} } // PKCEAuthorizationFlow represents Authorization Code Flow information @@ -1578,7 +1644,7 @@ type PKCEAuthorizationFlow struct { func (x *PKCEAuthorizationFlow) Reset() { *x = PKCEAuthorizationFlow{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[19] + mi := &file_management_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1591,7 +1657,7 @@ func (x *PKCEAuthorizationFlow) String() string { func (*PKCEAuthorizationFlow) ProtoMessage() {} func (x *PKCEAuthorizationFlow) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[19] + mi := &file_management_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1604,7 +1670,7 @@ func (x *PKCEAuthorizationFlow) ProtoReflect() protoreflect.Message { // Deprecated: Use PKCEAuthorizationFlow.ProtoReflect.Descriptor instead. func (*PKCEAuthorizationFlow) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{19} + return file_management_proto_rawDescGZIP(), []int{20} } func (x *PKCEAuthorizationFlow) GetProviderConfig() *ProviderConfig { @@ -1646,7 +1712,7 @@ type ProviderConfig struct { func (x *ProviderConfig) Reset() { *x = ProviderConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[20] + mi := &file_management_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1659,7 +1725,7 @@ func (x *ProviderConfig) String() string { func (*ProviderConfig) ProtoMessage() {} func (x *ProviderConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[20] + mi := &file_management_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1672,7 +1738,7 @@ func (x *ProviderConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderConfig.ProtoReflect.Descriptor instead. func (*ProviderConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{20} + return file_management_proto_rawDescGZIP(), []int{21} } func (x *ProviderConfig) GetClientID() string { @@ -1763,7 +1829,7 @@ type Route struct { func (x *Route) Reset() { *x = Route{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[21] + mi := &file_management_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1776,7 +1842,7 @@ func (x *Route) String() string { func (*Route) ProtoMessage() {} func (x *Route) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[21] + mi := &file_management_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1789,7 +1855,7 @@ func (x *Route) ProtoReflect() protoreflect.Message { // Deprecated: Use Route.ProtoReflect.Descriptor instead. func (*Route) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{21} + return file_management_proto_rawDescGZIP(), []int{22} } func (x *Route) GetID() string { @@ -1855,7 +1921,7 @@ type DNSConfig struct { func (x *DNSConfig) Reset() { *x = DNSConfig{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[22] + mi := &file_management_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1868,7 +1934,7 @@ func (x *DNSConfig) String() string { func (*DNSConfig) ProtoMessage() {} func (x *DNSConfig) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[22] + mi := &file_management_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1881,7 +1947,7 @@ func (x *DNSConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use DNSConfig.ProtoReflect.Descriptor instead. func (*DNSConfig) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{22} + return file_management_proto_rawDescGZIP(), []int{23} } func (x *DNSConfig) GetServiceEnable() bool { @@ -1918,7 +1984,7 @@ type CustomZone struct { func (x *CustomZone) Reset() { *x = CustomZone{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[23] + mi := &file_management_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1931,7 +1997,7 @@ func (x *CustomZone) String() string { func (*CustomZone) ProtoMessage() {} func (x *CustomZone) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[23] + mi := &file_management_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1944,7 +2010,7 @@ func (x *CustomZone) ProtoReflect() protoreflect.Message { // Deprecated: Use CustomZone.ProtoReflect.Descriptor instead. func (*CustomZone) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{23} + return file_management_proto_rawDescGZIP(), []int{24} } func (x *CustomZone) GetDomain() string { @@ -1977,7 +2043,7 @@ type SimpleRecord struct { func (x *SimpleRecord) Reset() { *x = SimpleRecord{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[24] + mi := &file_management_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1990,7 +2056,7 @@ func (x *SimpleRecord) String() string { func (*SimpleRecord) ProtoMessage() {} func (x *SimpleRecord) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[24] + mi := &file_management_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2003,7 +2069,7 @@ func (x *SimpleRecord) ProtoReflect() protoreflect.Message { // Deprecated: Use SimpleRecord.ProtoReflect.Descriptor instead. func (*SimpleRecord) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{24} + return file_management_proto_rawDescGZIP(), []int{25} } func (x *SimpleRecord) GetName() string { @@ -2056,7 +2122,7 @@ type NameServerGroup struct { func (x *NameServerGroup) Reset() { *x = NameServerGroup{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[25] + mi := &file_management_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2069,7 +2135,7 @@ func (x *NameServerGroup) String() string { func (*NameServerGroup) ProtoMessage() {} func (x *NameServerGroup) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[25] + mi := &file_management_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2082,7 +2148,7 @@ func (x *NameServerGroup) ProtoReflect() protoreflect.Message { // Deprecated: Use NameServerGroup.ProtoReflect.Descriptor instead. func (*NameServerGroup) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{25} + return file_management_proto_rawDescGZIP(), []int{26} } func (x *NameServerGroup) GetNameServers() []*NameServer { @@ -2127,7 +2193,7 @@ type NameServer struct { func (x *NameServer) Reset() { *x = NameServer{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[26] + mi := &file_management_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2140,7 +2206,7 @@ func (x *NameServer) String() string { func (*NameServer) ProtoMessage() {} func (x *NameServer) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[26] + mi := &file_management_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2153,7 +2219,7 @@ func (x *NameServer) ProtoReflect() protoreflect.Message { // Deprecated: Use NameServer.ProtoReflect.Descriptor instead. func (*NameServer) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{26} + return file_management_proto_rawDescGZIP(), []int{27} } func (x *NameServer) GetIP() string { @@ -2193,7 +2259,7 @@ type FirewallRule struct { func (x *FirewallRule) Reset() { *x = FirewallRule{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[27] + mi := &file_management_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2206,7 +2272,7 @@ func (x *FirewallRule) String() string { func (*FirewallRule) ProtoMessage() {} func (x *FirewallRule) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[27] + mi := &file_management_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2219,7 +2285,7 @@ func (x *FirewallRule) ProtoReflect() protoreflect.Message { // Deprecated: Use FirewallRule.ProtoReflect.Descriptor instead. func (*FirewallRule) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{27} + return file_management_proto_rawDescGZIP(), []int{28} } func (x *FirewallRule) GetPeerIP() string { @@ -2269,7 +2335,7 @@ type NetworkAddress struct { func (x *NetworkAddress) Reset() { *x = NetworkAddress{} if protoimpl.UnsafeEnabled { - mi := &file_management_proto_msgTypes[28] + mi := &file_management_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2282,7 +2348,7 @@ func (x *NetworkAddress) String() string { func (*NetworkAddress) ProtoMessage() {} func (x *NetworkAddress) ProtoReflect() protoreflect.Message { - mi := &file_management_proto_msgTypes[28] + mi := &file_management_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2295,7 +2361,7 @@ func (x *NetworkAddress) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkAddress.ProtoReflect.Descriptor instead. func (*NetworkAddress) Descriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{28} + return file_management_proto_rawDescGZIP(), []int{29} } func (x *NetworkAddress) GetNetIP() string { @@ -2360,291 +2426,299 @@ var file_management_proto_rawDesc = []byte{ 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0xee, 0x03, 0x0a, 0x0e, - 0x50, 0x65, 0x65, 0x72, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1a, - 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x6f, - 0x4f, 0x53, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x6f, 0x4f, 0x53, 0x12, 0x16, - 0x0a, 0x06, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, - 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, - 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x4f, 0x53, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x4f, 0x53, 0x12, 0x2e, 0x0a, 0x12, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x65, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x12, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x69, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x69, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6b, 0x65, 0x72, - 0x6e, 0x65, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x4f, 0x53, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x4f, - 0x53, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x10, 0x6e, 0x65, 0x74, 0x77, - 0x6f, 0x72, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x10, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, - 0x12, 0x28, 0x0a, 0x0f, 0x73, 0x79, 0x73, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, - 0x62, 0x65, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x79, 0x73, 0x53, 0x65, - 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x79, - 0x73, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x0d, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0e, 0x73, 0x79, 0x73, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x73, 0x79, 0x73, 0x4d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, - 0x74, 0x75, 0x72, 0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x79, 0x73, - 0x4d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, 0x22, 0x94, 0x01, 0x0a, - 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, - 0x0a, 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x57, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, - 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0a, 0x70, - 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, - 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x22, 0x79, 0x0a, 0x11, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x38, 0x0a, 0x09, 0x65, 0x78, - 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, - 0x65, 0x73, 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x07, - 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xa8, 0x01, 0x0a, 0x11, 0x57, 0x69, 0x72, 0x65, - 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x0a, - 0x05, 0x73, 0x74, 0x75, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x73, 0x74, 0x75, 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x05, 0x74, - 0x75, 0x72, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, - 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x74, 0x75, 0x72, - 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, - 0x61, 0x6c, 0x22, 0x98, 0x01, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x75, 0x72, 0x69, 0x12, 0x3b, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, - 0x22, 0x3b, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03, - 0x55, 0x44, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x08, - 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, - 0x53, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x54, 0x4c, 0x53, 0x10, 0x04, 0x22, 0x7d, 0x0a, - 0x13, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, - 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x81, 0x01, 0x0a, - 0x0a, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x64, 0x6e, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, - 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, - 0x22, 0xe2, 0x03, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x12, - 0x16, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, + 0x0c, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x3f, 0x0a, 0x0b, 0x45, + 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, + 0x6f, 0x75, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x22, 0xa9, 0x04, 0x0a, + 0x0e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x12, + 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x67, + 0x6f, 0x4f, 0x53, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x6f, 0x4f, 0x53, 0x12, + 0x16, 0x0a, 0x06, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, + 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x4f, 0x53, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x4f, 0x53, 0x12, 0x2e, 0x0a, 0x12, 0x77, 0x69, 0x72, 0x65, 0x74, + 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x12, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x69, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x69, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6b, 0x65, + 0x72, 0x6e, 0x65, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x4f, + 0x53, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x4f, 0x53, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x10, 0x6e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x0b, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, + 0x10, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, + 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x73, 0x79, 0x73, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, + 0x6d, 0x62, 0x65, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x79, 0x73, 0x53, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0e, 0x73, + 0x79, 0x73, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x0d, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x79, 0x73, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x73, 0x79, 0x73, 0x4d, 0x61, 0x6e, 0x75, 0x66, 0x61, + 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x79, + 0x73, 0x4d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72, 0x12, 0x39, 0x0a, + 0x0b, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, + 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0b, 0x65, 0x6e, 0x76, + 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x94, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, + 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x11, 0x77, 0x69, + 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x57, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, + 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x3e, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, - 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x72, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, - 0x29, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x52, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x44, 0x4e, - 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x4e, 0x53, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x40, 0x0a, 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, - 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x65, 0x65, 0x72, - 0x73, 0x12, 0x3e, 0x0a, 0x0d, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, - 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, - 0x6c, 0x65, 0x52, 0x0d, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, - 0x73, 0x12, 0x32, 0x0a, 0x14, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, - 0x65, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, + 0x79, 0x0a, 0x11, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x38, 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, + 0x73, 0x41, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x22, 0xa8, 0x01, 0x0a, 0x11, 0x57, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, + 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x0a, 0x05, 0x73, 0x74, 0x75, + 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x05, 0x73, 0x74, 0x75, 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x05, 0x74, 0x75, 0x72, 0x6e, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, + 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x12, 0x2e, + 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x22, 0x98, + 0x01, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, + 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, + 0x3b, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, + 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, 0x3b, 0x0a, 0x08, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, + 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, + 0x54, 0x50, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x03, 0x12, + 0x08, 0x0a, 0x04, 0x44, 0x54, 0x4c, 0x53, 0x10, 0x04, 0x22, 0x7d, 0x0a, 0x13, 0x50, 0x72, 0x6f, + 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x36, 0x0a, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x68, 0x6f, + 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, + 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x81, 0x01, 0x0a, 0x0a, 0x50, 0x65, 0x65, + 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x64, 0x6e, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, + 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0xe2, 0x03, 0x0a, + 0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x53, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x53, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3e, 0x0a, 0x0b, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, + 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, + 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, + 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x06, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, + 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x40, 0x0a, 0x0c, 0x6f, + 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x3e, 0x0a, + 0x0d, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x08, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, + 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x49, 0x73, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x97, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, - 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, - 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, - 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, - 0x64, 0x49, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, - 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, - 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, - 0x49, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x0a, - 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, - 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x20, 0x0a, 0x1e, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbf, 0x01, 0x0a, - 0x17, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x48, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x16, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x12, 0x0a, 0x0a, 0x06, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0x1e, - 0x0a, 0x1c, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x5b, - 0x0a, 0x15, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xea, 0x02, 0x0a, 0x0e, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, - 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, - 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, - 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, - 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, - 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, - 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, - 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1e, - 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0a, 0x55, 0x73, 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, - 0x0a, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x55, 0x52, 0x4c, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x52, 0x65, 0x64, 0x69, - 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a, 0x0b, - 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, - 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, - 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, - 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, - 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x65, - 0x74, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, - 0x22, 0xb4, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, - 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x12, 0x47, 0x0a, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, - 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x10, 0x4e, 0x61, 0x6d, - 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x38, 0x0a, - 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x0b, 0x43, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x22, 0x58, 0x0a, 0x0a, 0x43, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x32, 0x0a, - 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, - 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x69, 0x6d, 0x70, - 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x73, 0x22, 0x74, 0x0a, 0x0c, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x43, 0x6c, 0x61, - 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, - 0x10, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x54, 0x54, - 0x4c, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x22, 0xb3, 0x01, 0x0a, 0x0f, 0x4e, 0x61, 0x6d, 0x65, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x38, 0x0a, 0x0b, 0x4e, - 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, - 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, - 0x18, 0x0a, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x53, 0x65, 0x61, - 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x48, 0x0a, - 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, - 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x4e, - 0x53, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4e, 0x53, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xf0, 0x02, 0x0a, 0x0c, 0x46, 0x69, 0x72, 0x65, - 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, - 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, - 0x12, 0x40, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x64, 0x69, - 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x08, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, - 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, - 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, - 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x1c, - 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02, 0x49, - 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x22, 0x1e, 0x0a, 0x06, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, - 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, 0x22, 0x3c, 0x0a, 0x08, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, - 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x07, - 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x03, - 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x22, 0x38, 0x0a, 0x0e, 0x4e, 0x65, - 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, - 0x6e, 0x65, 0x74, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x65, 0x74, - 0x49, 0x50, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6d, 0x61, 0x63, 0x32, 0xd1, 0x03, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, - 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x66, 0x69, 0x72, + 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x22, 0x97, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, + 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, + 0x70, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, + 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0x49, 0x0a, 0x09, 0x53, + 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x45, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x73, + 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x50, + 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x73, 0x68, + 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x20, 0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, + 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x48, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x42, + 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x22, 0x16, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a, + 0x0a, 0x06, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0x1e, 0x0a, 0x1c, 0x50, 0x4b, + 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, + 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x5b, 0x0a, 0x15, 0x50, 0x4b, + 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, + 0x6c, 0x6f, 0x77, 0x12, 0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xea, 0x02, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e, + 0x0a, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, + 0x0a, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x55, 0x73, + 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, + 0x55, 0x73, 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x15, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x12, 0x22, 0x0a, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, + 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x55, 0x52, 0x4c, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e, + 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18, + 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x4e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, + 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x16, + 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, + 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, + 0x72, 0x61, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x4d, 0x61, 0x73, 0x71, + 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x22, 0xb4, 0x01, 0x0a, + 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x12, 0x47, 0x0a, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, + 0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x43, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, + 0x6e, 0x65, 0x73, 0x22, 0x58, 0x0a, 0x0a, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x32, 0x0a, 0x07, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x74, 0x0a, + 0x0c, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, + 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x54, + 0x54, 0x4c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x14, 0x0a, + 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x44, + 0x61, 0x74, 0x61, 0x22, 0xb3, 0x01, 0x0a, 0x0f, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x38, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x73, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x44, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x48, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, + 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, + 0x6f, 0x72, 0x74, 0x22, 0xf0, 0x02, 0x0a, 0x0c, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, + 0x52, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x12, 0x40, 0x0a, 0x09, + 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, + 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, + 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, + 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, + 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, + 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x1c, 0x0a, 0x09, 0x64, 0x69, + 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, + 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x22, 0x1e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, + 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, 0x22, 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, + 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, + 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, + 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x22, 0x38, 0x0a, 0x0e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x65, 0x74, 0x49, + 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x65, 0x74, 0x49, 0x50, 0x12, 0x10, + 0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x63, + 0x32, 0xd1, 0x03, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, + 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, + 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, + 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, - 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, - 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, - 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, - 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, - 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, - 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, - 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, + 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, + 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, + 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, - 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, 0x47, 0x65, + 0x74, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2660,7 +2734,7 @@ func file_management_proto_rawDescGZIP() []byte { } var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 5) -var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 29) +var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 30) var file_management_proto_goTypes = []interface{}{ (HostConfig_Protocol)(0), // 0: management.HostConfig.Protocol (DeviceAuthorizationFlowProvider)(0), // 1: management.DeviceAuthorizationFlow.provider @@ -2672,83 +2746,85 @@ var file_management_proto_goTypes = []interface{}{ (*SyncResponse)(nil), // 7: management.SyncResponse (*LoginRequest)(nil), // 8: management.LoginRequest (*PeerKeys)(nil), // 9: management.PeerKeys - (*PeerSystemMeta)(nil), // 10: management.PeerSystemMeta - (*LoginResponse)(nil), // 11: management.LoginResponse - (*ServerKeyResponse)(nil), // 12: management.ServerKeyResponse - (*Empty)(nil), // 13: management.Empty - (*WiretrusteeConfig)(nil), // 14: management.WiretrusteeConfig - (*HostConfig)(nil), // 15: management.HostConfig - (*ProtectedHostConfig)(nil), // 16: management.ProtectedHostConfig - (*PeerConfig)(nil), // 17: management.PeerConfig - (*NetworkMap)(nil), // 18: management.NetworkMap - (*RemotePeerConfig)(nil), // 19: management.RemotePeerConfig - (*SSHConfig)(nil), // 20: management.SSHConfig - (*DeviceAuthorizationFlowRequest)(nil), // 21: management.DeviceAuthorizationFlowRequest - (*DeviceAuthorizationFlow)(nil), // 22: management.DeviceAuthorizationFlow - (*PKCEAuthorizationFlowRequest)(nil), // 23: management.PKCEAuthorizationFlowRequest - (*PKCEAuthorizationFlow)(nil), // 24: management.PKCEAuthorizationFlow - (*ProviderConfig)(nil), // 25: management.ProviderConfig - (*Route)(nil), // 26: management.Route - (*DNSConfig)(nil), // 27: management.DNSConfig - (*CustomZone)(nil), // 28: management.CustomZone - (*SimpleRecord)(nil), // 29: management.SimpleRecord - (*NameServerGroup)(nil), // 30: management.NameServerGroup - (*NameServer)(nil), // 31: management.NameServer - (*FirewallRule)(nil), // 32: management.FirewallRule - (*NetworkAddress)(nil), // 33: management.NetworkAddress - (*timestamppb.Timestamp)(nil), // 34: google.protobuf.Timestamp + (*Environment)(nil), // 10: management.Environment + (*PeerSystemMeta)(nil), // 11: management.PeerSystemMeta + (*LoginResponse)(nil), // 12: management.LoginResponse + (*ServerKeyResponse)(nil), // 13: management.ServerKeyResponse + (*Empty)(nil), // 14: management.Empty + (*WiretrusteeConfig)(nil), // 15: management.WiretrusteeConfig + (*HostConfig)(nil), // 16: management.HostConfig + (*ProtectedHostConfig)(nil), // 17: management.ProtectedHostConfig + (*PeerConfig)(nil), // 18: management.PeerConfig + (*NetworkMap)(nil), // 19: management.NetworkMap + (*RemotePeerConfig)(nil), // 20: management.RemotePeerConfig + (*SSHConfig)(nil), // 21: management.SSHConfig + (*DeviceAuthorizationFlowRequest)(nil), // 22: management.DeviceAuthorizationFlowRequest + (*DeviceAuthorizationFlow)(nil), // 23: management.DeviceAuthorizationFlow + (*PKCEAuthorizationFlowRequest)(nil), // 24: management.PKCEAuthorizationFlowRequest + (*PKCEAuthorizationFlow)(nil), // 25: management.PKCEAuthorizationFlow + (*ProviderConfig)(nil), // 26: management.ProviderConfig + (*Route)(nil), // 27: management.Route + (*DNSConfig)(nil), // 28: management.DNSConfig + (*CustomZone)(nil), // 29: management.CustomZone + (*SimpleRecord)(nil), // 30: management.SimpleRecord + (*NameServerGroup)(nil), // 31: management.NameServerGroup + (*NameServer)(nil), // 32: management.NameServer + (*FirewallRule)(nil), // 33: management.FirewallRule + (*NetworkAddress)(nil), // 34: management.NetworkAddress + (*timestamppb.Timestamp)(nil), // 35: google.protobuf.Timestamp } var file_management_proto_depIdxs = []int32{ - 14, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig - 17, // 1: management.SyncResponse.peerConfig:type_name -> management.PeerConfig - 19, // 2: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig - 18, // 3: management.SyncResponse.NetworkMap:type_name -> management.NetworkMap - 10, // 4: management.LoginRequest.meta:type_name -> management.PeerSystemMeta + 15, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig + 18, // 1: management.SyncResponse.peerConfig:type_name -> management.PeerConfig + 20, // 2: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig + 19, // 3: management.SyncResponse.NetworkMap:type_name -> management.NetworkMap + 11, // 4: management.LoginRequest.meta:type_name -> management.PeerSystemMeta 9, // 5: management.LoginRequest.peerKeys:type_name -> management.PeerKeys - 33, // 6: management.PeerSystemMeta.networkAddresses:type_name -> management.NetworkAddress - 14, // 7: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig - 17, // 8: management.LoginResponse.peerConfig:type_name -> management.PeerConfig - 34, // 9: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp - 15, // 10: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig - 16, // 11: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig - 15, // 12: management.WiretrusteeConfig.signal:type_name -> management.HostConfig - 0, // 13: management.HostConfig.protocol:type_name -> management.HostConfig.Protocol - 15, // 14: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig - 20, // 15: management.PeerConfig.sshConfig:type_name -> management.SSHConfig - 17, // 16: management.NetworkMap.peerConfig:type_name -> management.PeerConfig - 19, // 17: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig - 26, // 18: management.NetworkMap.Routes:type_name -> management.Route - 27, // 19: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig - 19, // 20: management.NetworkMap.offlinePeers:type_name -> management.RemotePeerConfig - 32, // 21: management.NetworkMap.FirewallRules:type_name -> management.FirewallRule - 20, // 22: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig - 1, // 23: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider - 25, // 24: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig - 25, // 25: management.PKCEAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig - 30, // 26: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup - 28, // 27: management.DNSConfig.CustomZones:type_name -> management.CustomZone - 29, // 28: management.CustomZone.Records:type_name -> management.SimpleRecord - 31, // 29: management.NameServerGroup.NameServers:type_name -> management.NameServer - 2, // 30: management.FirewallRule.Direction:type_name -> management.FirewallRule.direction - 3, // 31: management.FirewallRule.Action:type_name -> management.FirewallRule.action - 4, // 32: management.FirewallRule.Protocol:type_name -> management.FirewallRule.protocol - 5, // 33: management.ManagementService.Login:input_type -> management.EncryptedMessage - 5, // 34: management.ManagementService.Sync:input_type -> management.EncryptedMessage - 13, // 35: management.ManagementService.GetServerKey:input_type -> management.Empty - 13, // 36: management.ManagementService.isHealthy:input_type -> management.Empty - 5, // 37: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage - 5, // 38: management.ManagementService.GetPKCEAuthorizationFlow:input_type -> management.EncryptedMessage - 5, // 39: management.ManagementService.Login:output_type -> management.EncryptedMessage - 5, // 40: management.ManagementService.Sync:output_type -> management.EncryptedMessage - 12, // 41: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse - 13, // 42: management.ManagementService.isHealthy:output_type -> management.Empty - 5, // 43: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage - 5, // 44: management.ManagementService.GetPKCEAuthorizationFlow:output_type -> management.EncryptedMessage - 39, // [39:45] is the sub-list for method output_type - 33, // [33:39] is the sub-list for method input_type - 33, // [33:33] is the sub-list for extension type_name - 33, // [33:33] is the sub-list for extension extendee - 0, // [0:33] is the sub-list for field type_name + 34, // 6: management.PeerSystemMeta.networkAddresses:type_name -> management.NetworkAddress + 10, // 7: management.PeerSystemMeta.environment:type_name -> management.Environment + 15, // 8: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig + 18, // 9: management.LoginResponse.peerConfig:type_name -> management.PeerConfig + 35, // 10: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp + 16, // 11: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig + 17, // 12: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig + 16, // 13: management.WiretrusteeConfig.signal:type_name -> management.HostConfig + 0, // 14: management.HostConfig.protocol:type_name -> management.HostConfig.Protocol + 16, // 15: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig + 21, // 16: management.PeerConfig.sshConfig:type_name -> management.SSHConfig + 18, // 17: management.NetworkMap.peerConfig:type_name -> management.PeerConfig + 20, // 18: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig + 27, // 19: management.NetworkMap.Routes:type_name -> management.Route + 28, // 20: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig + 20, // 21: management.NetworkMap.offlinePeers:type_name -> management.RemotePeerConfig + 33, // 22: management.NetworkMap.FirewallRules:type_name -> management.FirewallRule + 21, // 23: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig + 1, // 24: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider + 26, // 25: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig + 26, // 26: management.PKCEAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig + 31, // 27: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup + 29, // 28: management.DNSConfig.CustomZones:type_name -> management.CustomZone + 30, // 29: management.CustomZone.Records:type_name -> management.SimpleRecord + 32, // 30: management.NameServerGroup.NameServers:type_name -> management.NameServer + 2, // 31: management.FirewallRule.Direction:type_name -> management.FirewallRule.direction + 3, // 32: management.FirewallRule.Action:type_name -> management.FirewallRule.action + 4, // 33: management.FirewallRule.Protocol:type_name -> management.FirewallRule.protocol + 5, // 34: management.ManagementService.Login:input_type -> management.EncryptedMessage + 5, // 35: management.ManagementService.Sync:input_type -> management.EncryptedMessage + 14, // 36: management.ManagementService.GetServerKey:input_type -> management.Empty + 14, // 37: management.ManagementService.isHealthy:input_type -> management.Empty + 5, // 38: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage + 5, // 39: management.ManagementService.GetPKCEAuthorizationFlow:input_type -> management.EncryptedMessage + 5, // 40: management.ManagementService.Login:output_type -> management.EncryptedMessage + 5, // 41: management.ManagementService.Sync:output_type -> management.EncryptedMessage + 13, // 42: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse + 14, // 43: management.ManagementService.isHealthy:output_type -> management.Empty + 5, // 44: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage + 5, // 45: management.ManagementService.GetPKCEAuthorizationFlow:output_type -> management.EncryptedMessage + 40, // [40:46] is the sub-list for method output_type + 34, // [34:40] is the sub-list for method input_type + 34, // [34:34] is the sub-list for extension type_name + 34, // [34:34] is the sub-list for extension extendee + 0, // [0:34] is the sub-list for field type_name } func init() { file_management_proto_init() } @@ -2818,7 +2894,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeerSystemMeta); i { + switch v := v.(*Environment); i { case 0: return &v.state case 1: @@ -2830,7 +2906,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginResponse); i { + switch v := v.(*PeerSystemMeta); i { case 0: return &v.state case 1: @@ -2842,7 +2918,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ServerKeyResponse); i { + switch v := v.(*LoginResponse); i { case 0: return &v.state case 1: @@ -2854,7 +2930,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Empty); i { + switch v := v.(*ServerKeyResponse); i { case 0: return &v.state case 1: @@ -2866,7 +2942,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WiretrusteeConfig); i { + switch v := v.(*Empty); i { case 0: return &v.state case 1: @@ -2878,7 +2954,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HostConfig); i { + switch v := v.(*WiretrusteeConfig); i { case 0: return &v.state case 1: @@ -2890,7 +2966,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ProtectedHostConfig); i { + switch v := v.(*HostConfig); i { case 0: return &v.state case 1: @@ -2902,7 +2978,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeerConfig); i { + switch v := v.(*ProtectedHostConfig); i { case 0: return &v.state case 1: @@ -2914,7 +2990,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NetworkMap); i { + switch v := v.(*PeerConfig); i { case 0: return &v.state case 1: @@ -2926,7 +3002,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RemotePeerConfig); i { + switch v := v.(*NetworkMap); i { case 0: return &v.state case 1: @@ -2938,7 +3014,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SSHConfig); i { + switch v := v.(*RemotePeerConfig); i { case 0: return &v.state case 1: @@ -2950,7 +3026,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeviceAuthorizationFlowRequest); i { + switch v := v.(*SSHConfig); i { case 0: return &v.state case 1: @@ -2962,7 +3038,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeviceAuthorizationFlow); i { + switch v := v.(*DeviceAuthorizationFlowRequest); i { case 0: return &v.state case 1: @@ -2974,7 +3050,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PKCEAuthorizationFlowRequest); i { + switch v := v.(*DeviceAuthorizationFlow); i { case 0: return &v.state case 1: @@ -2986,7 +3062,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PKCEAuthorizationFlow); i { + switch v := v.(*PKCEAuthorizationFlowRequest); i { case 0: return &v.state case 1: @@ -2998,7 +3074,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ProviderConfig); i { + switch v := v.(*PKCEAuthorizationFlow); i { case 0: return &v.state case 1: @@ -3010,7 +3086,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Route); i { + switch v := v.(*ProviderConfig); i { case 0: return &v.state case 1: @@ -3022,7 +3098,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DNSConfig); i { + switch v := v.(*Route); i { case 0: return &v.state case 1: @@ -3034,7 +3110,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CustomZone); i { + switch v := v.(*DNSConfig); i { case 0: return &v.state case 1: @@ -3046,7 +3122,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SimpleRecord); i { + switch v := v.(*CustomZone); i { case 0: return &v.state case 1: @@ -3058,7 +3134,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NameServerGroup); i { + switch v := v.(*SimpleRecord); i { case 0: return &v.state case 1: @@ -3070,7 +3146,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NameServer); i { + switch v := v.(*NameServerGroup); i { case 0: return &v.state case 1: @@ -3082,7 +3158,7 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FirewallRule); i { + switch v := v.(*NameServer); i { case 0: return &v.state case 1: @@ -3094,6 +3170,18 @@ func file_management_proto_init() { } } file_management_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FirewallRule); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_management_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*NetworkAddress); i { case 0: return &v.state @@ -3112,7 +3200,7 @@ func file_management_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_management_proto_rawDesc, NumEnums: 5, - NumMessages: 29, + NumMessages: 30, NumExtensions: 0, NumServices: 1, }, diff --git a/management/proto/management.proto b/management/proto/management.proto index aeadafc2e..2cc0efa22 100644 --- a/management/proto/management.proto +++ b/management/proto/management.proto @@ -92,6 +92,14 @@ message PeerKeys { bytes wgPubKey = 2; } +// Environment is part of the PeerSystemMeta and describes the environment the agent is running in. +message Environment { + // cloud is the cloud provider the agent is running in if applicable. + string cloud = 1; + // platform is the platform the agent is running on if applicable. + string platform = 2; +} + // PeerSystemMeta is machine meta data like OS and version. message PeerSystemMeta { string hostname = 1; @@ -108,6 +116,7 @@ message PeerSystemMeta { string sysSerialNumber = 12; string sysProductName = 13; string sysManufacturer = 14; + Environment environment = 15; } message LoginResponse { diff --git a/management/server/account.go b/management/server/account.go index cfa94bead..56d4c262f 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -126,6 +126,7 @@ type AccountManager interface { SavePostureChecks(accountID, userID string, postureChecks *posture.Checks) error DeletePostureChecks(accountID, postureChecksID, userID string) error ListPostureChecks(accountID, userID string) ([]*posture.Checks, error) + GetIdpManager() idp.Manager UpdateIntegratedApprovalGroups(accountID string, userID string, groups []string) error GroupValidation(accountId string, groups []string) (bool, error) } @@ -209,6 +210,7 @@ type Account struct { // User.Id it was created by CreatedBy string + CreatedAt time.Time Domain string `gorm:"index"` DomainCategory string IsDomainPrimaryAccount bool @@ -703,6 +705,7 @@ func (a *Account) Copy() *Account { return &Account{ Id: a.Id, CreatedBy: a.CreatedBy, + CreatedAt: a.CreatedAt, Domain: a.Domain, DomainCategory: a.DomainCategory, IsDomainPrimaryAccount: a.IsDomainPrimaryAccount, @@ -922,6 +925,10 @@ func (am *DefaultAccountManager) GetExternalCacheManager() ExternalCacheManager return am.externalCacheManager } +func (am *DefaultAccountManager) GetIdpManager() idp.Manager { + return am.idpManager +} + // UpdateAccountSettings updates Account settings. // Only users with role UserRoleAdmin can update the account. // User that performs the update has to belong to the account. @@ -939,12 +946,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, unlock := am.Store.AcquireAccountLock(accountID) defer unlock() - account, err := am.Store.GetAccountByUser(userID) - if err != nil { - return nil, err - } - - err = additions.ValidateExtraSettings(newSettings.Extra, account.Settings.Extra, account.Peers, userID, accountID, am.eventStore) + account, err := am.Store.GetAccount(accountID) if err != nil { return nil, err } @@ -958,6 +960,11 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, return nil, status.Errorf(status.PermissionDenied, "user is not allowed to update account") } + err = additions.ValidateExtraSettings(newSettings.Extra, account.Settings.Extra, account.Peers, userID, accountID, am.eventStore) + if err != nil { + return nil, err + } + oldSettings := account.Settings if oldSettings.PeerLoginExpirationEnabled != newSettings.PeerLoginExpirationEnabled { event := activity.AccountPeerLoginExpirationEnabled @@ -1892,6 +1899,7 @@ func newAccountWithId(accountID, userID, domain string) *Account { acc := &Account{ Id: accountID, + CreatedAt: time.Now().UTC(), SetupKeys: setupKeys, Network: network, Peers: peers, diff --git a/management/server/account_test.go b/management/server/account_test.go index 1e698eb96..02d242e3a 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -32,8 +32,12 @@ func (MocIntegratedApproval) PreparePeer(accountID string, peer *nbpeer.Peer, pe return peer } -func (MocIntegratedApproval) SyncPeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) (*nbpeer.Peer, bool) { - return peer.Copy(), false +func (MocIntegratedApproval) IsRequiresApproval(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) bool { + return false +} + +func (MocIntegratedApproval) Stop() { + } func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Account, userID string) { @@ -104,6 +108,10 @@ func verifyNewAccountHasDefaultFields(t *testing.T, account *Account, createdBy t.Errorf("expecting newly created account to be created by user %s, got %s", createdBy, account.CreatedBy) } + if account.CreatedAt.IsZero() { + t.Errorf("expecting newly created account to have a non-zero creation time") + } + if account.Domain != domain { t.Errorf("expecting newly created account to have domain %s, got %s", domain, account.Domain) } @@ -1483,6 +1491,7 @@ func TestAccount_Copy(t *testing.T) { account := &Account{ Id: "account1", CreatedBy: "tester", + CreatedAt: time.Now().UTC(), Domain: "test.com", DomainCategory: "public", IsDomainPrimaryAccount: true, diff --git a/management/server/activity/event.go b/management/server/activity/event.go index 249547122..0e819c3a7 100644 --- a/management/server/activity/event.go +++ b/management/server/activity/event.go @@ -9,7 +9,7 @@ const ( ) // ActivityDescriber is an interface that describes an activity -type ActivityDescriber interface { +type ActivityDescriber interface { //nolint:revive StringCode() string Message() string } diff --git a/management/server/geolocation/database.go b/management/server/geolocation/database.go new file mode 100644 index 000000000..1bada6075 --- /dev/null +++ b/management/server/geolocation/database.go @@ -0,0 +1,210 @@ +package geolocation + +import ( + "encoding/csv" + "fmt" + "io" + "net/url" + "os" + "path" + "strconv" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +const ( + geoLiteCityTarGZURL = "https://pkgs.netbird.io/geolocation-dbs/GeoLite2-City/download?suffix=tar.gz" + geoLiteCityZipURL = "https://pkgs.netbird.io/geolocation-dbs/GeoLite2-City-CSV/download?suffix=zip" + geoLiteCitySha256TarURL = "https://pkgs.netbird.io/geolocation-dbs/GeoLite2-City/download?suffix=tar.gz.sha256" + geoLiteCitySha256ZipURL = "https://pkgs.netbird.io/geolocation-dbs/GeoLite2-City-CSV/download?suffix=zip.sha256" +) + +// loadGeolocationDatabases loads the MaxMind databases. +func loadGeolocationDatabases(dataDir string) error { + files := []string{MMDBFileName, GeoSqliteDBFile} + for _, file := range files { + exists, _ := fileExists(path.Join(dataDir, file)) + if exists { + continue + } + + switch file { + case MMDBFileName: + extractFunc := func(src string, dst string) error { + if err := decompressTarGzFile(src, dst); err != nil { + return err + } + return copyFile(path.Join(dst, MMDBFileName), path.Join(dataDir, MMDBFileName)) + } + if err := loadDatabase( + geoLiteCitySha256TarURL, + geoLiteCityTarGZURL, + extractFunc, + ); err != nil { + return err + } + + case GeoSqliteDBFile: + extractFunc := func(src string, dst string) error { + if err := decompressZipFile(src, dst); err != nil { + return err + } + extractedCsvFile := path.Join(dst, "GeoLite2-City-Locations-en.csv") + return importCsvToSqlite(dataDir, extractedCsvFile) + } + + if err := loadDatabase( + geoLiteCitySha256ZipURL, + geoLiteCityZipURL, + extractFunc, + ); err != nil { + return err + } + } + } + return nil +} + +// loadDatabase downloads a file from the specified URL and verifies its checksum. +// It then calls the extract function to perform additional processing on the extracted files. +func loadDatabase(checksumURL string, fileURL string, extractFunc func(src string, dst string) error) error { + temp, err := os.MkdirTemp(os.TempDir(), "geolite") + if err != nil { + return err + } + defer os.RemoveAll(temp) + + checksumFile := path.Join(temp, getDatabaseFileName(checksumURL)) + err = downloadFile(checksumURL, checksumFile) + if err != nil { + return err + } + + sha256sum, err := loadChecksumFromFile(checksumFile) + if err != nil { + return err + } + + dbFile := path.Join(temp, getDatabaseFileName(fileURL)) + err = downloadFile(fileURL, dbFile) + if err != nil { + return err + } + + if err := verifyChecksum(dbFile, sha256sum); err != nil { + return err + } + + return extractFunc(dbFile, temp) +} + +// importCsvToSqlite imports a CSV file into a SQLite database. +func importCsvToSqlite(dataDir string, csvFile string) error { + geonames, err := loadGeonamesCsv(csvFile) + if err != nil { + return err + } + + db, err := gorm.Open(sqlite.Open(path.Join(dataDir, GeoSqliteDBFile)), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Silent), + CreateBatchSize: 1000, + PrepareStmt: true, + }) + if err != nil { + return err + } + defer func() { + sql, err := db.DB() + if err != nil { + return + } + sql.Close() + }() + + if err := db.AutoMigrate(&GeoNames{}); err != nil { + return err + } + + return db.Create(geonames).Error +} + +func loadGeonamesCsv(filepath string) ([]GeoNames, error) { + f, err := os.Open(filepath) + if err != nil { + return nil, err + } + defer f.Close() + + reader := csv.NewReader(f) + records, err := reader.ReadAll() + if err != nil { + return nil, err + } + + var geoNames []GeoNames + for index, record := range records { + if index == 0 { + continue + } + geoNameID, err := strconv.Atoi(record[0]) + if err != nil { + return nil, err + } + + geoName := GeoNames{ + GeoNameID: geoNameID, + LocaleCode: record[1], + ContinentCode: record[2], + ContinentName: record[3], + CountryIsoCode: record[4], + CountryName: record[5], + Subdivision1IsoCode: record[6], + Subdivision1Name: record[7], + Subdivision2IsoCode: record[8], + Subdivision2Name: record[9], + CityName: record[10], + MetroCode: record[11], + TimeZone: record[12], + IsInEuropeanUnion: record[13], + } + geoNames = append(geoNames, geoName) + } + + return geoNames, nil +} + +// getDatabaseFileName extracts the file name from a given URL string. +func getDatabaseFileName(urlStr string) string { + u, err := url.Parse(urlStr) + if err != nil { + panic(err) + } + + ext := u.Query().Get("suffix") + fileName := fmt.Sprintf("%s.%s", path.Base(u.Path), ext) + return fileName +} + +// copyFile performs a file copy operation from the source file to the destination. +func copyFile(src string, dst string) error { + srcFile, err := os.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + + dstFile, err := os.Create(dst) + if err != nil { + return err + } + defer dstFile.Close() + + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return err + } + + return nil +} diff --git a/management/server/geolocation/geolocation.go b/management/server/geolocation/geolocation.go index de7a8af82..88cdfcb9f 100644 --- a/management/server/geolocation/geolocation.go +++ b/management/server/geolocation/geolocation.go @@ -2,9 +2,7 @@ package geolocation import ( "bytes" - "crypto/sha256" "fmt" - "io" "net" "os" "path" @@ -54,20 +52,23 @@ type Country struct { CountryName string } -func NewGeolocation(datadir string) (*Geolocation, error) { - mmdbPath := path.Join(datadir, MMDBFileName) +func NewGeolocation(dataDir string) (*Geolocation, error) { + if err := loadGeolocationDatabases(dataDir); err != nil { + return nil, fmt.Errorf("failed to load MaxMind databases: %v", err) + } + mmdbPath := path.Join(dataDir, MMDBFileName) db, err := openDB(mmdbPath) if err != nil { return nil, err } - sha256sum, err := getSha256sum(mmdbPath) + sha256sum, err := calculateFileSHA256(mmdbPath) if err != nil { return nil, err } - locationDB, err := NewSqliteStore(datadir) + locationDB, err := NewSqliteStore(dataDir) if err != nil { return nil, err } @@ -104,21 +105,6 @@ func openDB(mmdbPath string) (*maxminddb.Reader, error) { return db, nil } -func getSha256sum(mmdbPath string) ([]byte, error) { - f, err := os.Open(mmdbPath) - if err != nil { - return nil, err - } - defer f.Close() - - h := sha256.New() - if _, err := io.Copy(h, f); err != nil { - return nil, err - } - - return h.Sum(nil), nil -} - func (gl *Geolocation) Lookup(ip net.IP) (*Record, error) { gl.mux.RLock() defer gl.mux.RUnlock() @@ -189,7 +175,7 @@ func (gl *Geolocation) reloader() { log.Errorf("geonames db reload failed: %s", err) } - newSha256sum1, err := getSha256sum(gl.mmdbPath) + newSha256sum1, err := calculateFileSHA256(gl.mmdbPath) if err != nil { log.Errorf("failed to calculate sha256 sum for '%s': %s", gl.mmdbPath, err) continue @@ -198,7 +184,7 @@ func (gl *Geolocation) reloader() { // we check sum twice just to avoid possible case when we reload during update of the file // considering the frequency of file update (few times a week) checking sum twice should be enough time.Sleep(50 * time.Millisecond) - newSha256sum2, err := getSha256sum(gl.mmdbPath) + newSha256sum2, err := calculateFileSHA256(gl.mmdbPath) if err != nil { log.Errorf("failed to calculate sha256 sum for '%s': %s", gl.mmdbPath, err) continue diff --git a/management/server/geolocation/store.go b/management/server/geolocation/store.go index 9f3638a7c..74401d6ca 100644 --- a/management/server/geolocation/store.go +++ b/management/server/geolocation/store.go @@ -20,6 +20,27 @@ const ( GeoSqliteDBFile = "geonames.db" ) +type GeoNames struct { + GeoNameID int `gorm:"column:geoname_id"` + LocaleCode string `gorm:"column:locale_code"` + ContinentCode string `gorm:"column:continent_code"` + ContinentName string `gorm:"column:continent_name"` + CountryIsoCode string `gorm:"column:country_iso_code"` + CountryName string `gorm:"column:country_name"` + Subdivision1IsoCode string `gorm:"column:subdivision_1_iso_code"` + Subdivision1Name string `gorm:"column:subdivision_1_name"` + Subdivision2IsoCode string `gorm:"column:subdivision_2_iso_code"` + Subdivision2Name string `gorm:"column:subdivision_2_name"` + CityName string `gorm:"column:city_name"` + MetroCode string `gorm:"column:metro_code"` + TimeZone string `gorm:"column:time_zone"` + IsInEuropeanUnion string `gorm:"column:is_in_european_union"` +} + +func (*GeoNames) TableName() string { + return "geonames" +} + // SqliteStore represents a location storage backed by a Sqlite DB. type SqliteStore struct { db *gorm.DB @@ -37,7 +58,7 @@ func NewSqliteStore(dataDir string) (*SqliteStore, error) { return nil, err } - sha256sum, err := getSha256sum(file) + sha256sum, err := calculateFileSHA256(file) if err != nil { return nil, err } @@ -60,7 +81,7 @@ func (s *SqliteStore) GetAllCountries() ([]Country, error) { } var countries []Country - result := s.db.Table("geonames"). + result := s.db.Model(&GeoNames{}). Select("country_iso_code", "country_name"). Group("country_name"). Scan(&countries) @@ -81,7 +102,7 @@ func (s *SqliteStore) GetCitiesByCountry(countryISOCode string) ([]City, error) } var cities []City - result := s.db.Table("geonames"). + result := s.db.Model(&GeoNames{}). Select("geoname_id", "city_name"). Where("country_iso_code = ?", countryISOCode). Group("city_name"). @@ -98,7 +119,7 @@ func (s *SqliteStore) reload() error { s.mux.Lock() defer s.mux.Unlock() - newSha256sum1, err := getSha256sum(s.filePath) + newSha256sum1, err := calculateFileSHA256(s.filePath) if err != nil { log.Errorf("failed to calculate sha256 sum for '%s': %s", s.filePath, err) } @@ -107,7 +128,7 @@ func (s *SqliteStore) reload() error { // we check sum twice just to avoid possible case when we reload during update of the file // considering the frequency of file update (few times a week) checking sum twice should be enough time.Sleep(50 * time.Millisecond) - newSha256sum2, err := getSha256sum(s.filePath) + newSha256sum2, err := calculateFileSHA256(s.filePath) if err != nil { return fmt.Errorf("failed to calculate sha256 sum for '%s': %s", s.filePath, err) } diff --git a/management/server/geolocation/utils.go b/management/server/geolocation/utils.go new file mode 100644 index 000000000..bdbd4732d --- /dev/null +++ b/management/server/geolocation/utils.go @@ -0,0 +1,176 @@ +package geolocation + +import ( + "archive/tar" + "archive/zip" + "bufio" + "bytes" + "compress/gzip" + "crypto/sha256" + "errors" + "fmt" + "io" + "net/http" + "os" + "path" + "strings" +) + +// decompressTarGzFile decompresses a .tar.gz file. +func decompressTarGzFile(filepath, destDir string) error { + file, err := os.Open(filepath) + if err != nil { + return err + } + defer file.Close() + + gzipReader, err := gzip.NewReader(file) + if err != nil { + return err + } + defer gzipReader.Close() + + tarReader := tar.NewReader(gzipReader) + + for { + header, err := tarReader.Next() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + return err + } + + if header.Typeflag == tar.TypeReg { + outFile, err := os.Create(path.Join(destDir, path.Base(header.Name))) + if err != nil { + return err + } + + _, err = io.Copy(outFile, tarReader) // #nosec G110 + outFile.Close() + if err != nil { + return err + } + } + + } + + return nil +} + +// decompressZipFile decompresses a .zip file. +func decompressZipFile(filepath, destDir string) error { + r, err := zip.OpenReader(filepath) + if err != nil { + return err + } + defer r.Close() + + for _, f := range r.File { + if f.FileInfo().IsDir() { + continue + } + + outFile, err := os.Create(path.Join(destDir, path.Base(f.Name))) + if err != nil { + return err + } + + rc, err := f.Open() + if err != nil { + outFile.Close() + return err + } + + _, err = io.Copy(outFile, rc) // #nosec G110 + outFile.Close() + rc.Close() + if err != nil { + return err + } + } + + return nil +} + +// calculateFileSHA256 calculates the SHA256 checksum of a file. +func calculateFileSHA256(filepath string) ([]byte, error) { + file, err := os.Open(filepath) + if err != nil { + return nil, err + } + defer file.Close() + + h := sha256.New() + if _, err := io.Copy(h, file); err != nil { + return nil, err + } + + return h.Sum(nil), nil +} + +// loadChecksumFromFile loads the first checksum from a file. +func loadChecksumFromFile(filepath string) (string, error) { + file, err := os.Open(filepath) + if err != nil { + return "", err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + if scanner.Scan() { + parts := strings.Fields(scanner.Text()) + if len(parts) > 0 { + return parts[0], nil + } + } + if err := scanner.Err(); err != nil { + return "", err + } + + return "", nil +} + +// verifyChecksum compares the calculated SHA256 checksum of a file against the expected checksum. +func verifyChecksum(filepath, expectedChecksum string) error { + calculatedChecksum, err := calculateFileSHA256(filepath) + + fileCheckSum := fmt.Sprintf("%x", calculatedChecksum) + if err != nil { + return err + } + + if fileCheckSum != expectedChecksum { + return fmt.Errorf("checksum mismatch: expected %s, got %s", expectedChecksum, fileCheckSum) + } + + return nil +} + +// downloadFile downloads a file from a URL and saves it to a local file path. +func downloadFile(url, filepath string) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected error occurred while downloading the file: %s", string(bodyBytes)) + } + + out, err := os.Create(filepath) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, bytes.NewBuffer(bodyBytes)) + return err +} diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index a79fe6456..341d202b6 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -288,6 +288,10 @@ func extractPeerMeta(loginReq *proto.LoginRequest) nbpeer.PeerSystemMeta { SystemSerialNumber: loginReq.GetMeta().GetSysSerialNumber(), SystemProductName: loginReq.GetMeta().GetSysProductName(), SystemManufacturer: loginReq.GetMeta().GetSysManufacturer(), + Environment: nbpeer.Environment{ + Cloud: loginReq.GetMeta().GetEnvironment().GetCloud(), + Platform: loginReq.GetMeta().GetEnvironment().GetPlatform(), + }, } } diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index 5c05c15ad..b2ddfd5cc 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -121,7 +121,7 @@ components: description: Last time this user performed a login to the dashboard type: string format: date-time - example: 2023-05-05T09:00:35.477782Z + example: "2023-05-05T09:00:35.477782Z" auto_groups: description: Group IDs to auto-assign to peers registered by this user type: array @@ -259,7 +259,7 @@ components: description: Last time peer connected to Netbird's management service type: string format: date-time - example: 2023-05-05T10:05:26.420578Z + example: "2023-05-05T10:05:26.420578Z" os: description: Peer's operating system and version type: string @@ -313,7 +313,7 @@ components: description: Last time this peer performed log in (authentication). E.g., user authenticated. type: string format: date-time - example: 2023-05-05T09:00:35.477782Z + example: "2023-05-05T09:00:35.477782Z" approval_required: description: (Cloud only) Indicates whether peer needs approval type: boolean @@ -405,7 +405,7 @@ components: description: Setup Key expiration date type: string format: date-time - example: 2023-06-01T14:47:22.291057Z + example: "2023-06-01T14:47:22.291057Z" type: description: Setup key type, one-off for single time usage and reusable type: string @@ -426,7 +426,7 @@ components: description: Setup key last usage date type: string format: date-time - example: 2023-05-05T09:00:35.477782Z + example: "2023-05-05T09:00:35.477782Z" state: description: Setup key status, "valid", "overused","expired" or "revoked" type: string @@ -441,7 +441,7 @@ components: description: Setup key last update date type: string format: date-time - example: 2023-05-05T09:00:35.477782Z + example: "2023-05-05T09:00:35.477782Z" usage_limit: description: A number of times this key can be used. The value of 0 indicates the unlimited usage. type: integer @@ -522,7 +522,7 @@ components: description: Date the token expires type: string format: date-time - example: 2023-05-05T14:38:28.977616Z + example: "2023-05-05T14:38:28.977616Z" created_by: description: User ID of the user who created the token type: string @@ -531,12 +531,12 @@ components: description: Date the token was created type: string format: date-time - example: 2023-05-02T14:48:20.465209Z + example: "2023-05-02T14:48:20.465209Z" last_used: description: Date the token was last used type: string format: date-time - example: 2023-05-04T12:45:25.9723616Z + example: "2023-05-04T12:45:25.9723616Z" required: - id - name @@ -862,8 +862,8 @@ components: $ref: '#/components/schemas/OSVersionCheck' geo_location_check: $ref: '#/components/schemas/GeoLocationCheck' - private_network_check: - $ref: '#/components/schemas/PrivateNetworkCheck' + peer_network_range_check: + $ref: '#/components/schemas/PeerNetworkRangeCheck' NBVersionCheck: description: Posture check for the version of NetBird type: object @@ -934,16 +934,16 @@ components: required: - locations - action - PrivateNetworkCheck: - description: Posture check for allow or deny private network + PeerNetworkRangeCheck: + description: Posture check for allow or deny access based on peer local network addresses type: object properties: ranges: - description: List of private network ranges in CIDR notation + description: List of peer network ranges in CIDR notation type: array items: type: string - example: ["192.168.1.0/24", "10.0.0.0/8"] + example: ["192.168.1.0/24", "10.0.0.0/8", "2001:db8:1234:1a00::/56"] action: description: Action to take upon policy match type: string @@ -979,7 +979,7 @@ components: type: string example: "Germany" country_code: - $ref: '#/components/schemas/CountryCode' + $ref: '#/components/schemas/CountryCode' required: - country_name - country_code @@ -1197,7 +1197,7 @@ components: description: The date and time when the event occurred type: string format: date-time - example: 2023-05-05T10:04:37.473542Z + example: "2023-05-05T10:04:37.473542Z" activity: description: The activity that occurred during the event type: string diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index 28fe63c9d..c007663a4 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -74,6 +74,12 @@ const ( NameserverNsTypeUdp NameserverNsType = "udp" ) +// Defines values for PeerNetworkRangeCheckAction. +const ( + PeerNetworkRangeCheckActionAllow PeerNetworkRangeCheckAction = "allow" + PeerNetworkRangeCheckActionDeny PeerNetworkRangeCheckAction = "deny" +) + // Defines values for PolicyRuleAction. const ( PolicyRuleActionAccept PolicyRuleAction = "accept" @@ -116,12 +122,6 @@ const ( PolicyRuleUpdateProtocolUdp PolicyRuleUpdateProtocol = "udp" ) -// Defines values for PrivateNetworkCheckAction. -const ( - PrivateNetworkCheckActionAllow PrivateNetworkCheckAction = "allow" - PrivateNetworkCheckActionDeny PrivateNetworkCheckAction = "deny" -) - // Defines values for UserStatus. const ( UserStatusActive UserStatus = "active" @@ -199,8 +199,8 @@ type Checks struct { // OsVersionCheck Posture check for the version of operating system OsVersionCheck *OSVersionCheck `json:"os_version_check,omitempty"` - // PrivateNetworkCheck Posture check for allow or deny private network - PrivateNetworkCheck *PrivateNetworkCheck `json:"private_network_check,omitempty"` + // PeerNetworkRangeCheck Posture check for allow or deny access based on peer local network addresses + PeerNetworkRangeCheck *PeerNetworkRangeCheck `json:"peer_network_range_check,omitempty"` } // City Describe city geographical location information @@ -656,6 +656,18 @@ type PeerMinimum struct { Name string `json:"name"` } +// PeerNetworkRangeCheck Posture check for allow or deny access based on peer local network addresses +type PeerNetworkRangeCheck struct { + // Action Action to take upon policy match + Action PeerNetworkRangeCheckAction `json:"action"` + + // Ranges List of peer network ranges in CIDR notation + Ranges []string `json:"ranges"` +} + +// PeerNetworkRangeCheckAction Action to take upon policy match +type PeerNetworkRangeCheckAction string + // PeerRequest defines model for PeerRequest. type PeerRequest struct { // ApprovalRequired (Cloud only) Indicates whether peer needs approval @@ -898,18 +910,6 @@ type PostureCheckUpdate struct { Name string `json:"name"` } -// PrivateNetworkCheck Posture check for allow or deny private network -type PrivateNetworkCheck struct { - // Action Action to take upon policy match - Action PrivateNetworkCheckAction `json:"action"` - - // Ranges List of private network ranges in CIDR notation - Ranges []string `json:"ranges"` -} - -// PrivateNetworkCheckAction Action to take upon policy match -type PrivateNetworkCheckAction string - // Route defines model for Route. type Route struct { // Description Route description diff --git a/management/server/http/middleware/auth_middleware_test.go b/management/server/http/middleware/auth_middleware_test.go index 3e3e84194..588bcaf02 100644 --- a/management/server/http/middleware/auth_middleware_test.go +++ b/management/server/http/middleware/auth_middleware_test.go @@ -177,7 +177,10 @@ func TestAuthMiddleware_Handler(t *testing.T) { for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { if tc.shouldBypassAuth { - bypass.AddBypassPath(tc.path) + err := bypass.AddBypassPath(tc.path) + if err != nil { + t.Fatalf("failed to add bypass path: %v", err) + } } req := httptest.NewRequest("GET", "http://testing"+tc.path, nil) diff --git a/management/server/http/middleware/bypass/bypass.go b/management/server/http/middleware/bypass/bypass.go index 2f2652eb6..87b41c6fc 100644 --- a/management/server/http/middleware/bypass/bypass.go +++ b/management/server/http/middleware/bypass/bypass.go @@ -1,8 +1,12 @@ package bypass import ( + "fmt" "net/http" + "path" "sync" + + log "github.com/sirupsen/logrus" ) var byPassMutex sync.RWMutex @@ -11,10 +15,16 @@ var byPassMutex sync.RWMutex var bypassPaths = make(map[string]struct{}) // AddBypassPath adds an exact path to the list of paths that bypass middleware. -func AddBypassPath(path string) { +// Paths can include wildcards, such as /api/*. Paths are matched using path.Match. +// Returns an error if the path has invalid pattern. +func AddBypassPath(path string) error { byPassMutex.Lock() defer byPassMutex.Unlock() + if err := validatePath(path); err != nil { + return fmt.Errorf("validate: %w", err) + } bypassPaths[path] = struct{}{} + return nil } // RemovePath removes a path from the list of paths that bypass middleware. @@ -24,16 +34,41 @@ func RemovePath(path string) { delete(bypassPaths, path) } +// GetList returns a list of all bypass paths. +func GetList() []string { + byPassMutex.RLock() + defer byPassMutex.RUnlock() + + list := make([]string, 0, len(bypassPaths)) + for k := range bypassPaths { + list = append(list, k) + } + + return list +} + // ShouldBypass checks if the request path is one of the auth bypass paths and returns true if the middleware should be bypassed. // This can be used to bypass authz/authn middlewares for certain paths, such as webhooks that implement their own authentication. func ShouldBypass(requestPath string, h http.Handler, w http.ResponseWriter, r *http.Request) bool { byPassMutex.RLock() defer byPassMutex.RUnlock() - if _, ok := bypassPaths[requestPath]; ok { - h.ServeHTTP(w, r) - return true + for bypassPath := range bypassPaths { + matched, err := path.Match(bypassPath, requestPath) + if err != nil { + log.Errorf("Error matching path %s with %s from %s: %v", bypassPath, requestPath, GetList(), err) + continue + } + if matched { + h.ServeHTTP(w, r) + return true + } } return false } + +func validatePath(p string) error { + _, err := path.Match(p, "") + return err +} diff --git a/management/server/http/middleware/bypass/bypass_test.go b/management/server/http/middleware/bypass/bypass_test.go index efcfe1c3d..c65e6fa1f 100644 --- a/management/server/http/middleware/bypass/bypass_test.go +++ b/management/server/http/middleware/bypass/bypass_test.go @@ -11,6 +11,19 @@ import ( "github.com/netbirdio/netbird/management/server/http/middleware/bypass" ) +func TestGetList(t *testing.T) { + bypassPaths := []string{"/path1", "/path2", "/path3"} + + for _, path := range bypassPaths { + err := bypass.AddBypassPath(path) + require.NoError(t, err, "Adding bypass path should not fail") + } + + list := bypass.GetList() + + assert.ElementsMatch(t, bypassPaths, list, "Bypass path list did not match expected paths") +} + func TestAuthBypass(t *testing.T) { dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) @@ -31,6 +44,13 @@ func TestAuthBypass(t *testing.T) { expectBypass: true, expectHTTPCode: http.StatusOK, }, + { + name: "Wildcard path added to bypass", + pathToAdd: "/bypass/*", + testPath: "/bypass/extra", + expectBypass: true, + expectHTTPCode: http.StatusOK, + }, { name: "Path not added to bypass", testPath: "/no-bypass", @@ -59,6 +79,13 @@ func TestAuthBypass(t *testing.T) { expectBypass: false, expectHTTPCode: http.StatusOK, }, + { + name: "Wildcard subpath does not match bypass", + pathToAdd: "/webhook/*", + testPath: "/webhook/extra/path", + expectBypass: false, + expectHTTPCode: http.StatusOK, + }, { name: "Similar path does not match bypass", pathToAdd: "/webhook", @@ -78,7 +105,8 @@ func TestAuthBypass(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { if tc.pathToAdd != "" { - bypass.AddBypassPath(tc.pathToAdd) + err := bypass.AddBypassPath(tc.pathToAdd) + require.NoError(t, err, "Adding bypass path should not fail") defer bypass.RemovePath(tc.pathToAdd) } diff --git a/management/server/http/posture_checks_handler.go b/management/server/http/posture_checks_handler.go index 581bba2b7..fcccc1997 100644 --- a/management/server/http/posture_checks_handler.go +++ b/management/server/http/posture_checks_handler.go @@ -213,8 +213,8 @@ func (p *PostureChecksHandler) savePostureChecks( postureChecks.Checks.GeoLocationCheck = toPostureGeoLocationCheck(geoLocationCheck) } - if privateNetworkCheck := req.Checks.PrivateNetworkCheck; privateNetworkCheck != nil { - postureChecks.Checks.PrivateNetworkCheck, err = toPrivateNetworkCheck(privateNetworkCheck) + if peerNetworkRangeCheck := req.Checks.PeerNetworkRangeCheck; peerNetworkRangeCheck != nil { + postureChecks.Checks.PeerNetworkRangeCheck, err = toPeerNetworkRangeCheck(peerNetworkRangeCheck) if err != nil { util.WriteError(status.Errorf(status.InvalidArgument, "invalid network prefix"), w) return @@ -235,7 +235,7 @@ func validatePostureChecksUpdate(req api.PostureCheckUpdate) error { } if req.Checks == nil || (req.Checks.NbVersionCheck == nil && req.Checks.OsVersionCheck == nil && - req.Checks.GeoLocationCheck == nil && req.Checks.PrivateNetworkCheck == nil) { + req.Checks.GeoLocationCheck == nil && req.Checks.PeerNetworkRangeCheck == nil) { return status.Errorf(status.InvalidArgument, "posture checks shouldn't be empty") } @@ -278,17 +278,17 @@ func validatePostureChecksUpdate(req api.PostureCheckUpdate) error { } } - if privateNetworkCheck := req.Checks.PrivateNetworkCheck; privateNetworkCheck != nil { - if privateNetworkCheck.Action == "" { - return status.Errorf(status.InvalidArgument, "action for private network check shouldn't be empty") + if peerNetworkRangeCheck := req.Checks.PeerNetworkRangeCheck; peerNetworkRangeCheck != nil { + if peerNetworkRangeCheck.Action == "" { + return status.Errorf(status.InvalidArgument, "action for peer network range check shouldn't be empty") } - allowedActions := []api.PrivateNetworkCheckAction{api.PrivateNetworkCheckActionAllow, api.PrivateNetworkCheckActionDeny} - if !slices.Contains(allowedActions, privateNetworkCheck.Action) { - return status.Errorf(status.InvalidArgument, "action for private network check is not valid value") + allowedActions := []api.PeerNetworkRangeCheckAction{api.PeerNetworkRangeCheckActionAllow, api.PeerNetworkRangeCheckActionDeny} + if !slices.Contains(allowedActions, peerNetworkRangeCheck.Action) { + return status.Errorf(status.InvalidArgument, "action for peer network range check is not valid value") } - if len(privateNetworkCheck.Ranges) == 0 { - return status.Errorf(status.InvalidArgument, "network ranges for private network check shouldn't be empty") + if len(peerNetworkRangeCheck.Ranges) == 0 { + return status.Errorf(status.InvalidArgument, "network ranges for peer network range check shouldn't be empty") } } @@ -318,8 +318,8 @@ func toPostureChecksResponse(postureChecks *posture.Checks) *api.PostureCheck { checks.GeoLocationCheck = toGeoLocationCheckResponse(postureChecks.Checks.GeoLocationCheck) } - if postureChecks.Checks.PrivateNetworkCheck != nil { - checks.PrivateNetworkCheck = toPrivateNetworkCheckResponse(postureChecks.Checks.PrivateNetworkCheck) + if postureChecks.Checks.PeerNetworkRangeCheck != nil { + checks.PeerNetworkRangeCheck = toPeerNetworkRangeCheckResponse(postureChecks.Checks.PeerNetworkRangeCheck) } return &api.PostureCheck{ @@ -369,19 +369,19 @@ func toPostureGeoLocationCheck(apiGeoLocationCheck *api.GeoLocationCheck) *postu } } -func toPrivateNetworkCheckResponse(check *posture.PrivateNetworkCheck) *api.PrivateNetworkCheck { +func toPeerNetworkRangeCheckResponse(check *posture.PeerNetworkRangeCheck) *api.PeerNetworkRangeCheck { netPrefixes := make([]string, 0, len(check.Ranges)) for _, netPrefix := range check.Ranges { netPrefixes = append(netPrefixes, netPrefix.String()) } - return &api.PrivateNetworkCheck{ + return &api.PeerNetworkRangeCheck{ Ranges: netPrefixes, - Action: api.PrivateNetworkCheckAction(check.Action), + Action: api.PeerNetworkRangeCheckAction(check.Action), } } -func toPrivateNetworkCheck(check *api.PrivateNetworkCheck) (*posture.PrivateNetworkCheck, error) { +func toPeerNetworkRangeCheck(check *api.PeerNetworkRangeCheck) (*posture.PeerNetworkRangeCheck, error) { prefixes := make([]netip.Prefix, 0) for _, prefix := range check.Ranges { parsedPrefix, err := netip.ParsePrefix(prefix) @@ -391,7 +391,7 @@ func toPrivateNetworkCheck(check *api.PrivateNetworkCheck) (*posture.PrivateNetw prefixes = append(prefixes, parsedPrefix) } - return &posture.PrivateNetworkCheck{ + return &posture.PeerNetworkRangeCheck{ Ranges: prefixes, Action: string(check.Action), }, nil diff --git a/management/server/http/posture_checks_handler_test.go b/management/server/http/posture_checks_handler_test.go index 24a28f3ec..70e803214 100644 --- a/management/server/http/posture_checks_handler_test.go +++ b/management/server/http/posture_checks_handler_test.go @@ -131,7 +131,7 @@ func TestGetPostureCheck(t *testing.T) { ID: "privateNetworkPostureCheck", Name: "privateNetwork", Checks: posture.ChecksDefinition{ - PrivateNetworkCheck: &posture.PrivateNetworkCheck{ + PeerNetworkRangeCheck: &posture.PeerNetworkRangeCheck{ Ranges: []netip.Prefix{ netip.MustParsePrefix("192.168.0.0/24"), }, @@ -375,7 +375,7 @@ func TestPostureCheckUpdate(t *testing.T) { }, }, { - name: "Create Posture Checks Private Network", + name: "Create Posture Checks Peer Network Range", requestType: http.MethodPost, requestPath: "/api/posture-checks", requestBody: bytes.NewBuffer( @@ -383,7 +383,7 @@ func TestPostureCheckUpdate(t *testing.T) { "name": "default", "description": "default", "checks": { - "private_network_check": { + "peer_network_range_check": { "action": "allow", "ranges": [ "10.0.0.0/8" @@ -398,11 +398,11 @@ func TestPostureCheckUpdate(t *testing.T) { Name: "default", Description: str("default"), Checks: api.Checks{ - PrivateNetworkCheck: &api.PrivateNetworkCheck{ + PeerNetworkRangeCheck: &api.PeerNetworkRangeCheck{ Ranges: []string{ "10.0.0.0/8", }, - Action: api.PrivateNetworkCheckActionAllow, + Action: api.PeerNetworkRangeCheckActionAllow, }, }, }, @@ -715,14 +715,14 @@ func TestPostureCheckUpdate(t *testing.T) { expectedBody: false, }, { - name: "Update Posture Checks Private Network", + name: "Update Posture Checks Peer Network Range", requestType: http.MethodPut, - requestPath: "/api/posture-checks/privateNetworkPostureCheck", + requestPath: "/api/posture-checks/peerNetworkRangePostureCheck", requestBody: bytes.NewBuffer( []byte(`{ "name": "default", "checks": { - "private_network_check": { + "peer_network_range_check": { "action": "deny", "ranges": [ "192.168.1.0/24" @@ -737,11 +737,11 @@ func TestPostureCheckUpdate(t *testing.T) { Name: "default", Description: str(""), Checks: api.Checks{ - PrivateNetworkCheck: &api.PrivateNetworkCheck{ + PeerNetworkRangeCheck: &api.PeerNetworkRangeCheck{ Ranges: []string{ "192.168.1.0/24", }, - Action: api.PrivateNetworkCheckActionDeny, + Action: api.PeerNetworkRangeCheckActionDeny, }, }, }, @@ -784,10 +784,10 @@ func TestPostureCheckUpdate(t *testing.T) { }, }, &posture.Checks{ - ID: "privateNetworkPostureCheck", - Name: "privateNetwork", + ID: "peerNetworkRangePostureCheck", + Name: "peerNetworkRange", Checks: posture.ChecksDefinition{ - PrivateNetworkCheck: &posture.PrivateNetworkCheck{ + PeerNetworkRangeCheck: &posture.PeerNetworkRangeCheck{ Ranges: []netip.Prefix{ netip.MustParsePrefix("192.168.0.0/24"), }, @@ -891,29 +891,50 @@ func TestPostureCheck_validatePostureChecksUpdate(t *testing.T) { err = validatePostureChecksUpdate(api.PostureCheckUpdate{Name: "Default", Checks: &api.Checks{OsVersionCheck: &osVersionCheck}}) assert.NoError(t, err) - // valid private network check - privateNetworkCheck := api.PrivateNetworkCheck{ - Action: api.PrivateNetworkCheckActionAllow, + // valid peer network range check + peerNetworkRangeCheck := api.PeerNetworkRangeCheck{ + Action: api.PeerNetworkRangeCheckActionAllow, Ranges: []string{ "192.168.1.0/24", "10.0.0.0/8", }, } - err = validatePostureChecksUpdate(api.PostureCheckUpdate{Name: "Default", Checks: &api.Checks{PrivateNetworkCheck: &privateNetworkCheck}}) + err = validatePostureChecksUpdate( + api.PostureCheckUpdate{ + Name: "Default", + Checks: &api.Checks{ + PeerNetworkRangeCheck: &peerNetworkRangeCheck, + }, + }, + ) assert.NoError(t, err) - // invalid private network check - privateNetworkCheck = api.PrivateNetworkCheck{ - Action: api.PrivateNetworkCheckActionDeny, + // invalid peer network range check + peerNetworkRangeCheck = api.PeerNetworkRangeCheck{ + Action: api.PeerNetworkRangeCheckActionDeny, Ranges: []string{}, } - err = validatePostureChecksUpdate(api.PostureCheckUpdate{Name: "Default", Checks: &api.Checks{PrivateNetworkCheck: &privateNetworkCheck}}) + err = validatePostureChecksUpdate( + api.PostureCheckUpdate{ + Name: "Default", + Checks: &api.Checks{ + PeerNetworkRangeCheck: &peerNetworkRangeCheck, + }, + }, + ) assert.Error(t, err) - // invalid private network check - privateNetworkCheck = api.PrivateNetworkCheck{ + // invalid peer network range check + peerNetworkRangeCheck = api.PeerNetworkRangeCheck{ Action: "unknownAction", Ranges: []string{}, } - err = validatePostureChecksUpdate(api.PostureCheckUpdate{Name: "Default", Checks: &api.Checks{PrivateNetworkCheck: &privateNetworkCheck}}) + err = validatePostureChecksUpdate( + api.PostureCheckUpdate{ + Name: "Default", + Checks: &api.Checks{ + PeerNetworkRangeCheck: &peerNetworkRangeCheck, + }, + }, + ) assert.Error(t, err) } diff --git a/management/server/idp/auth0.go b/management/server/idp/auth0.go index 745136f62..34a5c0de5 100644 --- a/management/server/idp/auth0.go +++ b/management/server/idp/auth0.go @@ -114,6 +114,22 @@ type auth0Profile struct { LastLogin string `json:"last_login"` } +// Connections represents a single Auth0 connection +// https://auth0.com/docs/api/management/v2/connections/get-connections +type Connection struct { + Id string `json:"id"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + IsDomainConnection bool `json:"is_domain_connection"` + Realms []string `json:"realms"` + Metadata map[string]string `json:"metadata"` + Options ConnectionOptions `json:"options"` +} + +type ConnectionOptions struct { + DomainAliases []string `json:"domain_aliases"` +} + // NewAuth0Manager creates a new instance of the Auth0Manager func NewAuth0Manager(config Auth0ClientConfig, appMetrics telemetry.AppMetrics) (*Auth0Manager, error) { httpTransport := http.DefaultTransport.(*http.Transport).Clone() @@ -581,13 +597,13 @@ func (am *Auth0Manager) GetAllAccounts() (map[string][]*UserData, error) { body, err := io.ReadAll(jobResp.Body) if err != nil { - log.Debugf("Coudln't read export job response; %v", err) + log.Debugf("Couldn't read export job response; %v", err) return nil, err } err = am.helper.Unmarshal(body, &exportJobResp) if err != nil { - log.Debugf("Coudln't unmarshal export job response; %v", err) + log.Debugf("Couldn't unmarshal export job response; %v", err) return nil, err } @@ -635,7 +651,7 @@ func (am *Auth0Manager) GetUserByEmail(email string) ([]*UserData, error) { err = am.helper.Unmarshal(body, &userResp) if err != nil { - log.Debugf("Coudln't unmarshal export job response; %v", err) + log.Debugf("Couldn't unmarshal export job response; %v", err) return nil, err } @@ -684,13 +700,13 @@ func (am *Auth0Manager) CreateUser(email, name, accountID, invitedByEmail string body, err := io.ReadAll(resp.Body) if err != nil { - log.Debugf("Coudln't read export job response; %v", err) + log.Debugf("Couldn't read export job response; %v", err) return nil, err } err = am.helper.Unmarshal(body, &createResp) if err != nil { - log.Debugf("Coudln't unmarshal export job response; %v", err) + log.Debugf("Couldn't unmarshal export job response; %v", err) return nil, err } @@ -777,6 +793,56 @@ func (am *Auth0Manager) DeleteUser(userID string) error { return nil } +// GetAllConnections returns detailed list of all connections filtered by given params. +// Note this method is not part of the IDP Manager interface as this is Auth0 specific. +func (am *Auth0Manager) GetAllConnections(strategy []string) ([]Connection, error) { + var connections []Connection + + q := make(url.Values) + q.Set("strategy", strings.Join(strategy, ",")) + + req, err := am.createRequest(http.MethodGet, "/api/v2/connections?"+q.Encode(), nil) + if err != nil { + return connections, err + } + + resp, err := am.httpClient.Do(req) + if err != nil { + log.Debugf("execute get connections request: %v", err) + if am.appMetrics != nil { + am.appMetrics.IDPMetrics().CountRequestError() + } + return connections, err + } + + defer func() { + err = resp.Body.Close() + if err != nil { + log.Errorf("close get connections request body: %v", err) + } + }() + if resp.StatusCode != 200 { + if am.appMetrics != nil { + am.appMetrics.IDPMetrics().CountRequestStatusError() + } + return connections, fmt.Errorf("unable to get connections, statusCode %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Debugf("Couldn't read get connections response; %v", err) + return connections, err + } + + err = am.helper.Unmarshal(body, &connections) + if err != nil { + log.Debugf("Couldn't unmarshal get connection response; %v", err) + return connections, err + } + + return connections, err +} + // checkExportJobStatus checks the status of the job created at CreateExportUsersJob. // If the status is "completed", then return the downloadLink func (am *Auth0Manager) checkExportJobStatus(jobID string) (bool, string, error) { diff --git a/management/server/integrated_approval/interface.go b/management/server/integrated_approval/interface.go index bf1219f47..be09d2669 100644 --- a/management/server/integrated_approval/interface.go +++ b/management/server/integrated_approval/interface.go @@ -8,5 +8,6 @@ import ( // IntegratedApproval interface exists to avoid the circle dependencies type IntegratedApproval interface { PreparePeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) *nbpeer.Peer - SyncPeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) (*nbpeer.Peer, bool) + IsRequiresApproval(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) bool + Stop() } diff --git a/management/server/management_test.go b/management/server/management_test.go index af8f51d0a..bc74eded1 100644 --- a/management/server/management_test.go +++ b/management/server/management_test.go @@ -452,8 +452,12 @@ func (MocIntegratedApproval) PreparePeer(accountID string, peer *nbpeer.Peer, pe return peer } -func (MocIntegratedApproval) SyncPeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) (*nbpeer.Peer, bool) { - return peer.Copy(), false +func (MocIntegratedApproval) IsRequiresApproval(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) bool { + return false +} + +func (MocIntegratedApproval) Stop() { + } func loginPeerWithValidSetupKey(serverPubKey wgtypes.Key, key wgtypes.Key, client mgmtProto.ManagementServiceClient) *mgmtProto.LoginResponse { diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index a5628da46..32076cb1f 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -11,6 +11,7 @@ import ( nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server/activity" + "github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/jwtclaims" nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/posture" @@ -93,6 +94,7 @@ type MockAccountManager struct { DeletePostureChecksFunc func(accountID, postureChecksID, userID string) error ListPostureChecksFunc func(accountID, userID string) ([]*posture.Checks, error) GetUsageFunc func(ctx context.Context, accountID string, start, end time.Time) (*server.AccountUsageStats, error) + GetIdpManagerFunc func() idp.Manager UpdateIntegratedApprovalGroupsFunc func(accountID string, userID string, groups []string) error GroupValidationFunc func(accountId string, groups []string) (bool, error) } @@ -707,7 +709,7 @@ func (am *MockAccountManager) ListPostureChecks(accountID, userID string) ([]*po return nil, status.Errorf(codes.Unimplemented, "method ListPostureChecks is not implemented") } -// GetUsage mocks GetCurrentUsage of the AccountManager interface +// GetUsage mocks GetUsage of the AccountManager interface func (am *MockAccountManager) GetUsage(ctx context.Context, accountID string, start time.Time, end time.Time) (*server.AccountUsageStats, error) { if am.GetUsageFunc != nil { return am.GetUsageFunc(ctx, accountID, start, end) @@ -715,6 +717,14 @@ func (am *MockAccountManager) GetUsage(ctx context.Context, accountID string, st return nil, status.Errorf(codes.Unimplemented, "method GetUsage is not implemented") } +// GetIdpManager mocks GetIdpManager of the AccountManager interface +func (am *MockAccountManager) GetIdpManager() idp.Manager { + if am.GetIdpManagerFunc != nil { + return am.GetIdpManagerFunc() + } + return nil +} + // UpdateIntegratedApprovalGroups mocks UpdateIntegratedApprovalGroups of the AccountManager interface func (am *MockAccountManager) UpdateIntegratedApprovalGroups(accountID string, userID string, groups []string) error { if am.UpdateIntegratedApprovalGroupsFunc != nil { diff --git a/management/server/peer.go b/management/server/peer.go index 4a5a9c53e..b8ab3091d 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -408,6 +408,8 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P return nil, nil, err } + registrationTime := time.Now().UTC() + newPeer := &nbpeer.Peer{ ID: xid.New().String(), Key: peer.Key, @@ -417,10 +419,11 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P Name: peer.Meta.Hostname, DNSLabel: newLabel, UserID: userID, - Status: &nbpeer.PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: false, LastSeen: registrationTime}, SSHEnabled: false, SSHKey: peer.SSHKey, - LastLogin: time.Now().UTC(), + LastLogin: registrationTime, + CreatedAt: registrationTime, LoginExpirationEnabled: addedByUser, Ephemeral: ephemeral, } @@ -518,10 +521,9 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*nbpeer.Peer, *Network return nil, nil, status.Errorf(status.PermissionDenied, "peer login has expired, please log in once more") } - peer, updated := am.integratedPeerValidator.SyncPeer(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra) - - if updated { - account.UpdatePeer(peer) + requiresApproval := am.integratedPeerValidator.IsRequiresApproval(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra) + if peer.Status.RequiresApproval != requiresApproval { + peer.Status.RequiresApproval = requiresApproval err = am.Store.SaveAccount(account) if err != nil { return nil, nil, err @@ -594,12 +596,12 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw am.StoreEvent(login.UserID, peer.ID, account.Id, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain())) } - peer, updated := am.integratedPeerValidator.SyncPeer(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra) - if updated { + isRequiresApproval := am.integratedPeerValidator.IsRequiresApproval(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra) + if peer.Status.RequiresApproval != isRequiresApproval { shouldStoreAccount = true } - peer, updated = updatePeerMeta(peer, login.Meta, account) + peer, updated := updatePeerMeta(peer, login.Meta, account) if updated { shouldStoreAccount = true } diff --git a/management/server/peer/peer.go b/management/server/peer/peer.go index f45c1cb9e..e62843180 100644 --- a/management/server/peer/peer.go +++ b/management/server/peer/peer.go @@ -40,13 +40,15 @@ type Peer struct { LoginExpirationEnabled bool // LastLogin the time when peer performed last login operation LastLogin time.Time + // CreatedAt records the time the peer was created + CreatedAt time.Time // Indicate ephemeral peer attribute Ephemeral bool // Geo location based on connection IP Location Location `gorm:"embedded;embeddedPrefix:location_"` } -type PeerStatus struct { +type PeerStatus struct { //nolint:revive // LastSeen is the last time peer was connected to the management service LastSeen time.Time // Connected indicates whether peer is connected to the management service or not @@ -71,8 +73,14 @@ type NetworkAddress struct { Mac string } +// Environment is a system environment information +type Environment struct { + Cloud string + Platform string +} + // PeerSystemMeta is a metadata of a Peer machine system -type PeerSystemMeta struct { +type PeerSystemMeta struct { //nolint:revive Hostname string GoOS string Kernel string @@ -87,6 +95,7 @@ type PeerSystemMeta struct { SystemSerialNumber string SystemProductName string SystemManufacturer string + Environment Environment `gorm:"serializer:json"` } func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool { @@ -119,7 +128,9 @@ func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool { p.UIVersion == other.UIVersion && p.SystemSerialNumber == other.SystemSerialNumber && p.SystemProductName == other.SystemProductName && - p.SystemManufacturer == other.SystemManufacturer + p.SystemManufacturer == other.SystemManufacturer && + p.Environment.Cloud == other.Environment.Cloud && + p.Environment.Platform == other.Environment.Platform } // AddedWithSSOLogin indicates whether this peer has been added with an SSO login by a user. @@ -148,6 +159,7 @@ func (p *Peer) Copy() *Peer { SSHEnabled: p.SSHEnabled, LoginExpirationEnabled: p.LoginExpirationEnabled, LastLogin: p.LastLogin, + CreatedAt: p.CreatedAt, Ephemeral: p.Ephemeral, Location: p.Location, } @@ -204,7 +216,7 @@ func (p *Peer) FQDN(dnsDomain string) string { // EventMeta returns activity event meta related to the peer func (p *Peer) EventMeta(dnsDomain string) map[string]any { - return map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP} + return map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP, "created_at": p.CreatedAt} } // Copy PeerStatus diff --git a/management/server/posture/checks.go b/management/server/posture/checks.go index ee85be405..1b1f9d322 100644 --- a/management/server/posture/checks.go +++ b/management/server/posture/checks.go @@ -10,10 +10,10 @@ import ( ) const ( - NBVersionCheckName = "NBVersionCheck" - OSVersionCheckName = "OSVersionCheck" - GeoLocationCheckName = "GeoLocationCheck" - PrivateNetworkCheckName = "PrivateNetworkCheck" + NBVersionCheckName = "NBVersionCheck" + OSVersionCheckName = "OSVersionCheck" + GeoLocationCheckName = "GeoLocationCheck" + PeerNetworkRangeCheckName = "PeerNetworkRangeCheck" CheckActionAllow string = "allow" CheckActionDeny string = "deny" @@ -44,10 +44,10 @@ type Checks struct { // ChecksDefinition contains definition of actual check type ChecksDefinition struct { - NBVersionCheck *NBVersionCheck `json:",omitempty"` - OSVersionCheck *OSVersionCheck `json:",omitempty"` - GeoLocationCheck *GeoLocationCheck `json:",omitempty"` - PrivateNetworkCheck *PrivateNetworkCheck `json:",omitempty"` + NBVersionCheck *NBVersionCheck `json:",omitempty"` + OSVersionCheck *OSVersionCheck `json:",omitempty"` + GeoLocationCheck *GeoLocationCheck `json:",omitempty"` + PeerNetworkRangeCheck *PeerNetworkRangeCheck `json:",omitempty"` } // Copy returns a copy of a checks definition. @@ -85,13 +85,13 @@ func (cd ChecksDefinition) Copy() ChecksDefinition { } copy(cdCopy.GeoLocationCheck.Locations, geoCheck.Locations) } - if cd.PrivateNetworkCheck != nil { - privateNetCheck := cd.PrivateNetworkCheck - cdCopy.PrivateNetworkCheck = &PrivateNetworkCheck{ - Action: privateNetCheck.Action, - Ranges: make([]netip.Prefix, len(privateNetCheck.Ranges)), + if cd.PeerNetworkRangeCheck != nil { + peerNetRangeCheck := cd.PeerNetworkRangeCheck + cdCopy.PeerNetworkRangeCheck = &PeerNetworkRangeCheck{ + Action: peerNetRangeCheck.Action, + Ranges: make([]netip.Prefix, len(peerNetRangeCheck.Ranges)), } - copy(cdCopy.PrivateNetworkCheck.Ranges, privateNetCheck.Ranges) + copy(cdCopy.PeerNetworkRangeCheck.Ranges, peerNetRangeCheck.Ranges) } return cdCopy } @@ -130,8 +130,8 @@ func (pc *Checks) GetChecks() []Check { if pc.Checks.GeoLocationCheck != nil { checks = append(checks, pc.Checks.GeoLocationCheck) } - if pc.Checks.PrivateNetworkCheck != nil { - checks = append(checks, pc.Checks.PrivateNetworkCheck) + if pc.Checks.PeerNetworkRangeCheck != nil { + checks = append(checks, pc.Checks.PeerNetworkRangeCheck) } return checks } diff --git a/management/server/posture/checks_test.go b/management/server/posture/checks_test.go index fc36e7f12..d36d4f50c 100644 --- a/management/server/posture/checks_test.go +++ b/management/server/posture/checks_test.go @@ -254,7 +254,7 @@ func TestChecks_Copy(t *testing.T) { }, Action: CheckActionAllow, }, - PrivateNetworkCheck: &PrivateNetworkCheck{ + PeerNetworkRangeCheck: &PeerNetworkRangeCheck{ Ranges: []netip.Prefix{ netip.MustParsePrefix("192.168.0.0/24"), netip.MustParsePrefix("10.0.0.0/8"), diff --git a/management/server/posture/network.go b/management/server/posture/network.go index 8607d07aa..9bf969f4c 100644 --- a/management/server/posture/network.go +++ b/management/server/posture/network.go @@ -8,16 +8,16 @@ import ( nbpeer "github.com/netbirdio/netbird/management/server/peer" ) -type PrivateNetworkCheck struct { +type PeerNetworkRangeCheck struct { Action string Ranges []netip.Prefix `gorm:"serializer:json"` } -var _ Check = (*PrivateNetworkCheck)(nil) +var _ Check = (*PeerNetworkRangeCheck)(nil) -func (p *PrivateNetworkCheck) Check(peer nbpeer.Peer) (bool, error) { +func (p *PeerNetworkRangeCheck) Check(peer nbpeer.Peer) (bool, error) { if len(peer.Meta.NetworkAddresses) == 0 { - return false, fmt.Errorf("peer's does not contain private network addresses") + return false, fmt.Errorf("peer's does not contain peer network range addresses") } maskedPrefixes := make([]netip.Prefix, 0, len(p.Ranges)) @@ -34,7 +34,7 @@ func (p *PrivateNetworkCheck) Check(peer nbpeer.Peer) (bool, error) { case CheckActionAllow: return true, nil default: - return false, fmt.Errorf("invalid private network check action: %s", p.Action) + return false, fmt.Errorf("invalid peer network range check action: %s", p.Action) } } } @@ -46,9 +46,9 @@ func (p *PrivateNetworkCheck) Check(peer nbpeer.Peer) (bool, error) { return false, nil } - return false, fmt.Errorf("invalid private network check action: %s", p.Action) + return false, fmt.Errorf("invalid peer network range check action: %s", p.Action) } -func (p *PrivateNetworkCheck) Name() string { - return PrivateNetworkCheckName +func (p *PeerNetworkRangeCheck) Name() string { + return PeerNetworkRangeCheckName } diff --git a/management/server/posture/network_test.go b/management/server/posture/network_test.go index 018005460..36ead4660 100644 --- a/management/server/posture/network_test.go +++ b/management/server/posture/network_test.go @@ -9,17 +9,17 @@ import ( nbpeer "github.com/netbirdio/netbird/management/server/peer" ) -func TestPrivateNetworkCheck_Check(t *testing.T) { +func TestPeerNetworkRangeCheck_Check(t *testing.T) { tests := []struct { name string - check PrivateNetworkCheck + check PeerNetworkRangeCheck peer nbpeer.Peer wantErr bool isValid bool }{ { - name: "Peer private networks matches the allowed range", - check: PrivateNetworkCheck{ + name: "Peer networks range matches the allowed range", + check: PeerNetworkRangeCheck{ Action: CheckActionAllow, Ranges: []netip.Prefix{ netip.MustParsePrefix("192.168.0.0/24"), @@ -42,8 +42,8 @@ func TestPrivateNetworkCheck_Check(t *testing.T) { isValid: true, }, { - name: "Peer private networks doesn't matches the allowed range", - check: PrivateNetworkCheck{ + name: "Peer networks range doesn't matches the allowed range", + check: PeerNetworkRangeCheck{ Action: CheckActionAllow, Ranges: []netip.Prefix{ netip.MustParsePrefix("192.168.0.0/24"), @@ -63,8 +63,8 @@ func TestPrivateNetworkCheck_Check(t *testing.T) { isValid: false, }, { - name: "Peer with no privates network in the allow range", - check: PrivateNetworkCheck{ + name: "Peer with no network range in the allow range", + check: PeerNetworkRangeCheck{ Action: CheckActionAllow, Ranges: []netip.Prefix{ netip.MustParsePrefix("192.168.0.0/16"), @@ -76,8 +76,8 @@ func TestPrivateNetworkCheck_Check(t *testing.T) { isValid: false, }, { - name: "Peer private networks matches the denied range", - check: PrivateNetworkCheck{ + name: "Peer networks range matches the denied range", + check: PeerNetworkRangeCheck{ Action: CheckActionDeny, Ranges: []netip.Prefix{ netip.MustParsePrefix("192.168.0.0/24"), @@ -100,8 +100,8 @@ func TestPrivateNetworkCheck_Check(t *testing.T) { isValid: false, }, { - name: "Peer private networks doesn't matches the denied range", - check: PrivateNetworkCheck{ + name: "Peer networks range doesn't matches the denied range", + check: PeerNetworkRangeCheck{ Action: CheckActionDeny, Ranges: []netip.Prefix{ netip.MustParsePrefix("192.168.0.0/24"), @@ -121,8 +121,8 @@ func TestPrivateNetworkCheck_Check(t *testing.T) { isValid: true, }, { - name: "Peer with no private networks in the denied range", - check: PrivateNetworkCheck{ + name: "Peer with no networks range in the denied range", + check: PeerNetworkRangeCheck{ Action: CheckActionDeny, Ranges: []netip.Prefix{ netip.MustParsePrefix("192.168.0.0/16"), diff --git a/management/server/scheduler.go b/management/server/scheduler.go index a35bdc30c..356348056 100644 --- a/management/server/scheduler.go +++ b/management/server/scheduler.go @@ -1,9 +1,10 @@ package server import ( - log "github.com/sirupsen/logrus" "sync" "time" + + log "github.com/sirupsen/logrus" ) // Scheduler is an interface which implementations can schedule and cancel jobs @@ -55,14 +56,8 @@ func (wm *DefaultScheduler) cancel(ID string) bool { cancel, ok := wm.jobs[ID] if ok { delete(wm.jobs, ID) - select { - case cancel <- struct{}{}: - log.Debugf("cancelled scheduled job %s", ID) - default: - log.Warnf("couldn't cancel job %s because there was no routine listening on the cancel event", ID) - return false - } - + close(cancel) + log.Debugf("cancelled scheduled job %s", ID) } return ok } @@ -90,25 +85,41 @@ func (wm *DefaultScheduler) Schedule(in time.Duration, ID string, job func() (ne return } + ticker := time.NewTicker(in) + wm.jobs[ID] = cancel log.Debugf("scheduled a job %s to run in %s. There are %d total jobs scheduled.", ID, in.String(), len(wm.jobs)) go func() { - select { - case <-time.After(in): - log.Debugf("time to do a scheduled job %s", ID) - runIn, reschedule := job() - wm.mu.Lock() - defer wm.mu.Unlock() - delete(wm.jobs, ID) - if reschedule { - go wm.Schedule(runIn, ID, job) + for { + select { + case <-ticker.C: + select { + case <-cancel: + log.Debugf("scheduled job %s was canceled, stop timer", ID) + ticker.Stop() + return + default: + log.Debugf("time to do a scheduled job %s", ID) + } + runIn, reschedule := job() + if !reschedule { + wm.mu.Lock() + defer wm.mu.Unlock() + delete(wm.jobs, ID) + log.Debugf("job %s is not scheduled to run again", ID) + ticker.Stop() + return + } + // we need this comparison to avoid resetting the ticker with the same duration and missing the current elapsesed time + if runIn != in { + ticker.Reset(runIn) + } + case <-cancel: + log.Debugf("job %s was canceled, stopping timer", ID) + ticker.Stop() + return } - case <-cancel: - log.Debugf("stopped scheduled job %s ", ID) - wm.mu.Lock() - defer wm.mu.Unlock() - delete(wm.jobs, ID) - return } + }() } diff --git a/management/server/scheduler_test.go b/management/server/scheduler_test.go index 0c0cef99b..4b2c2e30d 100644 --- a/management/server/scheduler_test.go +++ b/management/server/scheduler_test.go @@ -2,11 +2,12 @@ package server import ( "fmt" - "github.com/stretchr/testify/assert" "math/rand" "sync" "testing" "time" + + "github.com/stretchr/testify/assert" ) func TestScheduler_Performance(t *testing.T) { @@ -36,15 +37,24 @@ func TestScheduler_Cancel(t *testing.T) { jobID1 := "test-scheduler-job-1" jobID2 := "test-scheduler-job-2" scheduler := NewDefaultScheduler() - scheduler.Schedule(2*time.Second, jobID1, func() (nextRunIn time.Duration, reschedule bool) { - return 0, false + tChan := make(chan struct{}) + p := []string{jobID1, jobID2} + scheduler.Schedule(2*time.Millisecond, jobID1, func() (nextRunIn time.Duration, reschedule bool) { + tt := p[0] + <-tChan + t.Logf("job %s", tt) + return 2 * time.Millisecond, true }) - scheduler.Schedule(2*time.Second, jobID2, func() (nextRunIn time.Duration, reschedule bool) { - return 0, false + scheduler.Schedule(2*time.Millisecond, jobID2, func() (nextRunIn time.Duration, reschedule bool) { + return 2 * time.Millisecond, true }) + time.Sleep(4 * time.Millisecond) assert.Len(t, scheduler.jobs, 2) scheduler.Cancel([]string{jobID1}) + close(tChan) + p = []string{} + time.Sleep(4 * time.Millisecond) assert.Len(t, scheduler.jobs, 1) assert.NotNil(t, scheduler.jobs[jobID2]) } diff --git a/management/server/user.go b/management/server/user.go index 651488f2b..f1516139b 100644 --- a/management/server/user.go +++ b/management/server/user.go @@ -85,6 +85,8 @@ type User struct { Blocked bool // LastLogin is the last time the user logged in to IdP LastLogin time.Time + // CreatedAt records the time the user was created + CreatedAt time.Time // Issued of the user Issued string `gorm:"default:api"` @@ -173,6 +175,7 @@ func (u *User) Copy() *User { PATs: pats, Blocked: u.Blocked, LastLogin: u.LastLogin, + CreatedAt: u.CreatedAt, Issued: u.Issued, IntegrationReference: u.IntegrationReference, } @@ -188,6 +191,7 @@ func NewUser(id string, role UserRole, isServiceUser bool, nonDeletable bool, se ServiceUserName: serviceUserName, AutoGroups: autoGroups, Issued: issued, + CreatedAt: time.Now().UTC(), } } @@ -338,6 +342,7 @@ func (am *DefaultAccountManager) inviteNewUser(accountID, userID string, invite AutoGroups: invite.AutoGroups, Issued: invite.Issued, IntegrationReference: invite.IntegrationReference, + CreatedAt: time.Now().UTC(), } account.Users[idpUser.ID] = newUser @@ -414,7 +419,7 @@ func (am *DefaultAccountManager) ListUsers(accountID string) ([]*User, error) { } func (am *DefaultAccountManager) deleteServiceUser(account *Account, initiatorUserID string, targetUser *User) { - meta := map[string]any{"name": targetUser.ServiceUserName} + meta := map[string]any{"name": targetUser.ServiceUserName, "created_at": targetUser.CreatedAt} am.StoreEvent(initiatorUserID, targetUser.Id, account.Id, activity.ServiceUserDeleted, meta) delete(account.Users, targetUser.Id) } @@ -494,13 +499,23 @@ func (am *DefaultAccountManager) deleteRegularUser(account *Account, initiatorUs return err } + u, err := account.FindUser(targetUserID) + if err != nil { + log.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(account) if err != nil { return err } - meta := map[string]any{"name": tuName, "email": tuEmail} + meta := map[string]any{"name": tuName, "email": tuEmail, "created_at": tuCreatedAt} am.StoreEvent(initiatorUserID, targetUserID, account.Id, activity.UserDeleted, meta) am.updateAccountPeers(account) diff --git a/management/server/user_test.go b/management/server/user_test.go index 8de2267b3..50cd726ef 100644 --- a/management/server/user_test.go +++ b/management/server/user_test.go @@ -273,7 +273,8 @@ func TestUser_Copy(t *testing.T) { }, }, Blocked: false, - LastLogin: time.Now(), + LastLogin: time.Now().UTC(), + CreatedAt: time.Now().UTC(), Issued: "test", IntegrationReference: IntegrationReference{ ID: 0, diff --git a/signal/client/grpc.go b/signal/client/grpc.go index 07276aef1..7531608c3 100644 --- a/signal/client/grpc.go +++ b/signal/client/grpc.go @@ -21,11 +21,10 @@ import ( "google.golang.org/grpc/status" "github.com/netbirdio/netbird/encryption" + "github.com/netbirdio/netbird/management/client" "github.com/netbirdio/netbird/signal/proto" ) -const defaultSendTimeout = 5 * time.Second - // ConnStateNotifier is a wrapper interface of the status recorder type ConnStateNotifier interface { MarkSignalDisconnected(error) @@ -71,7 +70,7 @@ func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled boo transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})) } - sigCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + sigCtx, cancel := context.WithTimeout(ctx, client.ConnectTimeout) defer cancel() conn, err := grpc.DialContext( sigCtx, @@ -353,7 +352,7 @@ func (c *GrpcClient) Send(msg *proto.Message) error { return err } - attemptTimeout := defaultSendTimeout + attemptTimeout := client.ConnectTimeout for attempt := 0; attempt < 4; attempt++ { if attempt > 1 { diff --git a/signal/cmd/run.go b/signal/cmd/run.go index 9b52fb52d..10a2da636 100644 --- a/signal/cmd/run.go +++ b/signal/cmd/run.go @@ -4,7 +4,6 @@ import ( "errors" "flag" "fmt" - "golang.org/x/crypto/acme/autocert" "io" "io/fs" "net" @@ -14,10 +13,14 @@ import ( "strings" "time" + "golang.org/x/crypto/acme/autocert" + "github.com/netbirdio/netbird/encryption" "github.com/netbirdio/netbird/signal/proto" "github.com/netbirdio/netbird/signal/server" "github.com/netbirdio/netbird/util" + "github.com/netbirdio/netbird/version" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "google.golang.org/grpc" @@ -129,6 +132,7 @@ var ( log.Infof("running gRPC server: %s", grpcListener.Addr().String()) } + log.Infof("signal server version %s", version.NetbirdVersion()) log.Infof("started Signal Service") SetupCloseHandler()