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 | -|---------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|----------------------------------------------------------------------------|---------------------------------------| -| | | | | -| | | | | -| | | | | -| | | | | -| | | | | -| | | | | -| | | | | -| | | | | -| | | | | - - +| Connectivity | Management | Security | Automation | Platforms | +|------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------| +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| | |
  • - \[x] [Periodic re-authentication](https://docs.netbird.io/how-to/enforce-periodic-user-authentication)
  • | | | +| | | | | | ### 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 621afce9f..80550aa37 100644 Binary files a/client/ui/netbird-systemtray-connected.ico and b/client/ui/netbird-systemtray-connected.ico differ diff --git a/client/ui/netbird-systemtray-connected.png b/client/ui/netbird-systemtray-connected.png index c5878d018..f4d156da8 100644 Binary files a/client/ui/netbird-systemtray-connected.png and b/client/ui/netbird-systemtray-connected.png differ diff --git a/client/ui/netbird-systemtray-default.ico b/client/ui/netbird-systemtray-default.ico deleted file mode 100644 index 5a0252675..000000000 Binary files a/client/ui/netbird-systemtray-default.ico and /dev/null differ diff --git a/client/ui/netbird-systemtray-default.png b/client/ui/netbird-systemtray-default.png deleted file mode 100644 index 12e7a2dc1..000000000 Binary files a/client/ui/netbird-systemtray-default.png and /dev/null differ diff --git a/client/ui/netbird-systemtray-disconnected.ico b/client/ui/netbird-systemtray-disconnected.ico new file mode 100644 index 000000000..aa75268b0 Binary files /dev/null and b/client/ui/netbird-systemtray-disconnected.ico differ diff --git a/client/ui/netbird-systemtray-disconnected.png b/client/ui/netbird-systemtray-disconnected.png new file mode 100644 index 000000000..0e1b7275f Binary files /dev/null and b/client/ui/netbird-systemtray-disconnected.png differ diff --git a/client/ui/netbird-systemtray-update-connected.ico b/client/ui/netbird-systemtray-update-connected.ico new file mode 100644 index 000000000..cc056e68e Binary files /dev/null and b/client/ui/netbird-systemtray-update-connected.ico differ diff --git a/client/ui/netbird-systemtray-update-connected.png b/client/ui/netbird-systemtray-update-connected.png new file mode 100644 index 000000000..a0c453340 Binary files /dev/null and b/client/ui/netbird-systemtray-update-connected.png differ diff --git a/client/ui/netbird-systemtray-update-disconnected.ico b/client/ui/netbird-systemtray-update-disconnected.ico new file mode 100644 index 000000000..04c35b058 Binary files /dev/null and b/client/ui/netbird-systemtray-update-disconnected.ico differ diff --git a/client/ui/netbird-systemtray-update-disconnected.png b/client/ui/netbird-systemtray-update-disconnected.png new file mode 100644 index 000000000..44a30dc9a Binary files /dev/null and b/client/ui/netbird-systemtray-update-disconnected.png differ diff --git a/client/ui/netbird-systemtray-update.ico b/client/ui/netbird-systemtray-update.ico deleted file mode 100644 index 1a1c4086d..000000000 Binary files a/client/ui/netbird-systemtray-update.ico and /dev/null differ diff --git a/client/ui/netbird-systemtray-update.png b/client/ui/netbird-systemtray-update.png deleted file mode 100644 index 1f4651df9..000000000 Binary files a/client/ui/netbird-systemtray-update.png and /dev/null differ 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()