From 270f0e4ce89f8014150c887af1735c86f5783b02 Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Mon, 7 Nov 2022 15:38:21 +0100 Subject: [PATCH] Feature/dns protocol (#543) Added DNS update protocol message Added sync to clients Update nameserver API with new fields Added default NS groups Added new dns-name flag for the management service append to peer DNS label --- client/cmd/testutil.go | 2 +- client/internal/dns/mockServer.go | 35 ++ client/internal/dns/server.go | 39 +- client/internal/dns/server_test.go | 36 +- client/internal/engine.go | 57 +- client/internal/engine_test.go | 185 +++++- dns/dns.go | 34 +- management/client/client_test.go | 2 +- management/cmd/defaults.go | 18 + management/cmd/management.go | 8 +- management/cmd/root.go | 41 +- management/proto/management.pb.go | 653 +++++++++++++++++---- management/proto/management.proto | 39 ++ management/server/account.go | 31 +- management/server/account_test.go | 2 +- management/server/dns.go | 152 +++++ management/server/grpcserver.go | 30 +- management/server/http/api/openapi.yml | 6 +- management/server/http/api/types.gen.go | 5 + management/server/http/nameservers.go | 14 + management/server/http/nameservers_test.go | 3 + management/server/http/peers.go | 1 + management/server/management_proto_test.go | 2 +- management/server/management_test.go | 2 +- management/server/nameserver.go | 25 + management/server/nameserver_test.go | 60 +- management/server/network.go | 8 +- management/server/peer.go | 66 ++- management/server/route_test.go | 2 +- 29 files changed, 1316 insertions(+), 242 deletions(-) create mode 100644 client/internal/dns/mockServer.go create mode 100644 management/cmd/defaults.go create mode 100644 management/server/dns.go diff --git a/client/cmd/testutil.go b/client/cmd/testutil.go index 2ded1cac0..861a69bf8 100644 --- a/client/cmd/testutil.go +++ b/client/cmd/testutil.go @@ -68,7 +68,7 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste } peersUpdateManager := mgmt.NewPeersUpdateManager() - accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "") + accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "") if err != nil { t.Fatal(err) } diff --git a/client/internal/dns/mockServer.go b/client/internal/dns/mockServer.go new file mode 100644 index 000000000..ce02ed88e --- /dev/null +++ b/client/internal/dns/mockServer.go @@ -0,0 +1,35 @@ +package dns + +import ( + "fmt" + nbdns "github.com/netbirdio/netbird/dns" +) + +// MockServer is the mock instance of a dns server +type MockServer struct { + StartFunc func() + StopFunc func() + UpdateDNSServerFunc func(serial uint64, update nbdns.Config) error +} + +// Start mock implementation of Start from Server interface +func (m *MockServer) Start() { + if m.StartFunc != nil { + m.StartFunc() + } +} + +// Stop mock implementation of Stop from Server interface +func (m *MockServer) Stop() { + if m.StopFunc != nil { + m.StopFunc() + } +} + +// UpdateDNSServer mock implementation of UpdateDNSServer from Server interface +func (m *MockServer) UpdateDNSServer(serial uint64, update nbdns.Config) error { + if m.UpdateDNSServerFunc != nil { + return m.UpdateDNSServerFunc(serial, update) + } + return fmt.Errorf("method UpdateDNSServer is not implemented") +} diff --git a/client/internal/dns/server.go b/client/internal/dns/server.go index d8e8fc4be..67f3788ea 100644 --- a/client/internal/dns/server.go +++ b/client/internal/dns/server.go @@ -15,8 +15,15 @@ const ( defaultIP = "0.0.0.0" ) -// Server dns server object -type Server struct { +// Server is a dns server interface +type Server interface { + Start() + Stop() + UpdateDNSServer(serial uint64, update nbdns.Config) error +} + +// DefaultServer dns server object +type DefaultServer struct { ctx context.Context stop context.CancelFunc mux sync.Mutex @@ -35,8 +42,8 @@ type muxUpdate struct { handler dns.Handler } -// NewServer returns a new dns server -func NewServer(ctx context.Context) *Server { +// NewDefaultServer returns a new dns server +func NewDefaultServer(ctx context.Context) *DefaultServer { mux := dns.NewServeMux() dnsServer := &dns.Server{ @@ -48,7 +55,7 @@ func NewServer(ctx context.Context) *Server { ctx, stop := context.WithCancel(ctx) - return &Server{ + return &DefaultServer{ ctx: ctx, stop: stop, server: dnsServer, @@ -61,7 +68,7 @@ func NewServer(ctx context.Context) *Server { } // Start runs the listener in a go routine -func (s *Server) Start() { +func (s *DefaultServer) Start() { log.Debugf("starting dns on %s:%d", defaultIP, port) go func() { s.setListenerStatus(true) @@ -73,12 +80,12 @@ func (s *Server) Start() { }() } -func (s *Server) setListenerStatus(running bool) { +func (s *DefaultServer) setListenerStatus(running bool) { s.listenerIsRunning = running } // Stop stops the server -func (s *Server) Stop() { +func (s *DefaultServer) Stop() { s.stop() err := s.stopListener() @@ -87,7 +94,7 @@ func (s *Server) Stop() { } } -func (s *Server) stopListener() error { +func (s *DefaultServer) stopListener() error { if !s.listenerIsRunning { return nil } @@ -103,7 +110,7 @@ func (s *Server) stopListener() error { } // UpdateDNSServer processes an update received from the management service -func (s *Server) UpdateDNSServer(serial uint64, update nbdns.Update) error { +func (s *DefaultServer) UpdateDNSServer(serial uint64, update nbdns.Config) error { select { case <-s.ctx.Done(): log.Infof("not updating DNS server as context is closed") @@ -147,7 +154,7 @@ func (s *Server) UpdateDNSServer(serial uint64, update nbdns.Update) error { } } -func (s *Server) buildLocalHandlerUpdate(customZones []nbdns.CustomZone) ([]muxUpdate, map[string]nbdns.SimpleRecord, error) { +func (s *DefaultServer) buildLocalHandlerUpdate(customZones []nbdns.CustomZone) ([]muxUpdate, map[string]nbdns.SimpleRecord, error) { var muxUpdates []muxUpdate localRecords := make(map[string]nbdns.SimpleRecord, 0) @@ -169,7 +176,7 @@ func (s *Server) buildLocalHandlerUpdate(customZones []nbdns.CustomZone) ([]muxU return muxUpdates, localRecords, nil } -func (s *Server) buildUpstreamHandlerUpdate(nameServerGroups []nbdns.NameServerGroup) ([]muxUpdate, error) { +func (s *DefaultServer) buildUpstreamHandlerUpdate(nameServerGroups []*nbdns.NameServerGroup) ([]muxUpdate, error) { var muxUpdates []muxUpdate for _, nsGroup := range nameServerGroups { if len(nsGroup.NameServers) == 0 { @@ -219,7 +226,7 @@ func (s *Server) buildUpstreamHandlerUpdate(nameServerGroups []nbdns.NameServerG return muxUpdates, nil } -func (s *Server) updateMux(muxUpdates []muxUpdate) { +func (s *DefaultServer) updateMux(muxUpdates []muxUpdate) { muxUpdateMap := make(registrationMap) for _, update := range muxUpdates { @@ -237,7 +244,7 @@ func (s *Server) updateMux(muxUpdates []muxUpdate) { s.dnsMuxMap = muxUpdateMap } -func (s *Server) updateLocalResolver(update map[string]nbdns.SimpleRecord) { +func (s *DefaultServer) updateLocalResolver(update map[string]nbdns.SimpleRecord) { for key := range s.localResolver.registeredMap { _, found := update[key] if !found { @@ -261,10 +268,10 @@ func getNSHostPort(ns nbdns.NameServer) string { return fmt.Sprintf("%s:%d", ns.IP.String(), ns.Port) } -func (s *Server) registerMux(pattern string, handler dns.Handler) { +func (s *DefaultServer) registerMux(pattern string, handler dns.Handler) { s.dnsMux.Handle(pattern, handler) } -func (s *Server) deregisterMux(pattern string) { +func (s *DefaultServer) deregisterMux(pattern string) { s.dnsMux.HandleRemove(pattern) } diff --git a/client/internal/dns/server_test.go b/client/internal/dns/server_test.go index 43c35aa7e..6bbfef507 100644 --- a/client/internal/dns/server_test.go +++ b/client/internal/dns/server_test.go @@ -43,18 +43,18 @@ func TestUpdateDNSServer(t *testing.T) { initLocalMap registrationMap initSerial uint64 inputSerial uint64 - inputUpdate nbdns.Update + inputUpdate nbdns.Config shouldFail bool expectedUpstreamMap registrationMap expectedLocalMap registrationMap }{ { - name: "Initial Update Should Succeed", + name: "Initial Config Should Succeed", initLocalMap: make(registrationMap), initUpstreamMap: make(registrationMap), initSerial: 0, inputSerial: 1, - inputUpdate: nbdns.Update{ + inputUpdate: nbdns.Config{ ServiceEnable: true, CustomZones: []nbdns.CustomZone{ { @@ -62,7 +62,7 @@ func TestUpdateDNSServer(t *testing.T) { Records: zoneRecords, }, }, - NameServerGroups: []nbdns.NameServerGroup{ + NameServerGroups: []*nbdns.NameServerGroup{ { Domains: []string{"netbird.io"}, NameServers: nameServers, @@ -77,12 +77,12 @@ func TestUpdateDNSServer(t *testing.T) { expectedLocalMap: registrationMap{zoneRecords[0].Name: struct{}{}}, }, { - name: "New Update Should Succeed", + name: "New Config Should Succeed", initLocalMap: registrationMap{"netbird.cloud": struct{}{}}, initUpstreamMap: registrationMap{zoneRecords[0].Name: struct{}{}}, initSerial: 0, inputSerial: 1, - inputUpdate: nbdns.Update{ + inputUpdate: nbdns.Config{ ServiceEnable: true, CustomZones: []nbdns.CustomZone{ { @@ -90,7 +90,7 @@ func TestUpdateDNSServer(t *testing.T) { Records: zoneRecords, }, }, - NameServerGroups: []nbdns.NameServerGroup{ + NameServerGroups: []*nbdns.NameServerGroup{ { Domains: []string{"netbird.io"}, NameServers: nameServers, @@ -101,7 +101,7 @@ func TestUpdateDNSServer(t *testing.T) { expectedLocalMap: registrationMap{zoneRecords[0].Name: struct{}{}}, }, { - name: "Smaller Update Serial Should Be Skipped", + name: "Smaller Config Serial Should Be Skipped", initLocalMap: make(registrationMap), initUpstreamMap: make(registrationMap), initSerial: 2, @@ -114,7 +114,7 @@ func TestUpdateDNSServer(t *testing.T) { initUpstreamMap: make(registrationMap), initSerial: 0, inputSerial: 1, - inputUpdate: nbdns.Update{ + inputUpdate: nbdns.Config{ ServiceEnable: true, CustomZones: []nbdns.CustomZone{ { @@ -122,7 +122,7 @@ func TestUpdateDNSServer(t *testing.T) { Records: zoneRecords, }, }, - NameServerGroups: []nbdns.NameServerGroup{ + NameServerGroups: []*nbdns.NameServerGroup{ { NameServers: nameServers, }, @@ -136,7 +136,7 @@ func TestUpdateDNSServer(t *testing.T) { initUpstreamMap: make(registrationMap), initSerial: 0, inputSerial: 1, - inputUpdate: nbdns.Update{ + inputUpdate: nbdns.Config{ ServiceEnable: true, CustomZones: []nbdns.CustomZone{ { @@ -144,7 +144,7 @@ func TestUpdateDNSServer(t *testing.T) { Records: zoneRecords, }, }, - NameServerGroups: []nbdns.NameServerGroup{ + NameServerGroups: []*nbdns.NameServerGroup{ { NameServers: nameServers, }, @@ -158,14 +158,14 @@ func TestUpdateDNSServer(t *testing.T) { initUpstreamMap: make(registrationMap), initSerial: 0, inputSerial: 1, - inputUpdate: nbdns.Update{ + inputUpdate: nbdns.Config{ ServiceEnable: true, CustomZones: []nbdns.CustomZone{ { Domain: "netbird.cloud", }, }, - NameServerGroups: []nbdns.NameServerGroup{ + NameServerGroups: []*nbdns.NameServerGroup{ { NameServers: nameServers, Primary: true, @@ -175,12 +175,12 @@ func TestUpdateDNSServer(t *testing.T) { shouldFail: true, }, { - name: "Empty Update Should Succeed and Clean Maps", + name: "Empty Config Should Succeed and Clean Maps", initLocalMap: registrationMap{"netbird.cloud": struct{}{}}, initUpstreamMap: registrationMap{zoneRecords[0].Name: struct{}{}}, initSerial: 0, inputSerial: 1, - inputUpdate: nbdns.Update{ServiceEnable: true}, + inputUpdate: nbdns.Config{ServiceEnable: true}, expectedUpstreamMap: make(registrationMap), expectedLocalMap: make(registrationMap), }, @@ -189,7 +189,7 @@ func TestUpdateDNSServer(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { ctx := context.Background() - dnsServer := NewServer(ctx) + dnsServer := NewDefaultServer(ctx) dnsServer.dnsMuxMap = testCase.initUpstreamMap dnsServer.localResolver.registeredMap = testCase.initLocalMap @@ -231,7 +231,7 @@ func TestUpdateDNSServer(t *testing.T) { func TestDNSServerStartStop(t *testing.T) { ctx := context.Background() - dnsServer := NewServer(ctx) + dnsServer := NewDefaultServer(ctx) if runtime.GOOS == "windows" && os.Getenv("CI") == "true" { // todo review why this test is not working only on github actions workflows t.Skip("skipping test in Windows CI workflows.") diff --git a/client/internal/engine.go b/client/internal/engine.go index b94519e67..7f7e52de5 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -7,9 +7,11 @@ import ( "github.com/netbirdio/netbird/client/internal/routemanager" nbssh "github.com/netbirdio/netbird/client/ssh" nbstatus "github.com/netbirdio/netbird/client/status" + nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/route" "math/rand" "net" + "net/netip" "reflect" "runtime" "strings" @@ -105,7 +107,7 @@ type Engine struct { routeManager routemanager.Manager - dnsServer *dns.Server + dnsServer dns.Server } // Peer is an instance of the Connection Peer @@ -133,7 +135,7 @@ func NewEngine( networkSerial: 0, sshServerFunc: nbssh.DefaultSSHServer, statusRecorder: statusRecorder, - dnsServer: dns.NewServer(ctx), + dnsServer: dns.NewDefaultServer(ctx), } } @@ -646,6 +648,15 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error { log.Errorf("failed to update routes, err: %v", err) } + protoDNSConfig := networkMap.GetDNSConfig() + if protoDNSConfig == nil { + protoDNSConfig = &mgmProto.DNSConfig{} + } + err = e.dnsServer.UpdateDNSServer(serial, toDNSConfig(protoDNSConfig)) + if err != nil { + log.Errorf("failed to update dns server, err: %v", err) + } + e.networkSerial = serial return nil } @@ -668,6 +679,48 @@ func toRoutes(protoRoutes []*mgmProto.Route) []*route.Route { return routes } +func toDNSConfig(protoDNSConfig *mgmProto.DNSConfig) nbdns.Config { + dnsUpdate := nbdns.Config{ + ServiceEnable: protoDNSConfig.GetServiceEnable(), + CustomZones: make([]nbdns.CustomZone, 0), + NameServerGroups: make([]*nbdns.NameServerGroup, 0), + } + + for _, zone := range protoDNSConfig.GetCustomZones() { + dnsZone := nbdns.CustomZone{ + Domain: zone.GetDomain(), + } + for _, record := range zone.Records { + dnsRecord := nbdns.SimpleRecord{ + Name: record.GetName(), + Type: int(record.GetType()), + Class: record.GetClass(), + TTL: int(record.GetTTL()), + RData: record.GetRData(), + } + dnsZone.Records = append(dnsZone.Records, dnsRecord) + } + dnsUpdate.CustomZones = append(dnsUpdate.CustomZones, dnsZone) + } + + for _, nsGroup := range protoDNSConfig.GetNameServerGroups() { + dnsNSGroup := &nbdns.NameServerGroup{ + Primary: nsGroup.GetPrimary(), + Domains: nsGroup.GetDomains(), + } + for _, ns := range nsGroup.GetNameServers() { + dnsNS := nbdns.NameServer{ + IP: netip.MustParseAddr(ns.GetIP()), + NSType: nbdns.NameServerType(ns.GetNSType()), + Port: int(ns.GetPort()), + } + dnsNSGroup.NameServers = append(dnsNSGroup.NameServers, dnsNS) + } + dnsUpdate.NameServerGroups = append(dnsUpdate.NameServerGroups, dnsNSGroup) + } + return dnsUpdate +} + // addNewPeers adds peers that were not know before but arrived from the Management service with the update func (e *Engine) addNewPeers(peersUpdate []*mgmProto.RemotePeerConfig) error { for _, p := range peersUpdate { diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index 279d9c14f..e2b542ced 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -3,9 +3,11 @@ package internal import ( "context" "fmt" + "github.com/netbirdio/netbird/client/internal/dns" "github.com/netbirdio/netbird/client/internal/routemanager" "github.com/netbirdio/netbird/client/ssh" nbstatus "github.com/netbirdio/netbird/client/status" + nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/iface" "github.com/netbirdio/netbird/route" "github.com/stretchr/testify/assert" @@ -440,7 +442,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) { expectedSerial uint64 }{ { - name: "Routes Update Should Be Passed To Manager", + name: "Routes Config Should Be Passed To Manager", networkMap: &mgmtProto.NetworkMap{ Serial: 1, PeerConfig: nil, @@ -486,7 +488,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) { expectedSerial: 1, }, { - name: "Empty Routes Update Should Be Passed", + name: "Empty Routes Config Should Be Passed", networkMap: &mgmtProto.NetworkMap{ Serial: 1, PeerConfig: nil, @@ -566,6 +568,183 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) { } } +func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) { + testCases := []struct { + name string + inputErr error + networkMap *mgmtProto.NetworkMap + expectedZonesLen int + expectedZones []nbdns.CustomZone + expectedNSGroupsLen int + expectedNSGroups []*nbdns.NameServerGroup + expectedSerial uint64 + }{ + { + name: "DNS Config Should Be Passed To DNS Server", + networkMap: &mgmtProto.NetworkMap{ + Serial: 1, + PeerConfig: nil, + RemotePeersIsEmpty: false, + Routes: nil, + DNSConfig: &mgmtProto.DNSConfig{ + ServiceEnable: true, + CustomZones: []*mgmtProto.CustomZone{ + { + Domain: "netbird.cloud.", + Records: []*mgmtProto.SimpleRecord{ + { + Name: "peer-a.netbird.cloud.", + Type: 1, + Class: nbdns.DefaultClass, + TTL: 300, + RData: "100.64.0.1", + }, + }, + }, + }, + NameServerGroups: []*mgmtProto.NameServerGroup{ + { + Primary: true, + NameServers: []*mgmtProto.NameServer{ + { + IP: "8.8.8.8", + NSType: 1, + Port: 53, + }, + }, + }, + }, + }, + }, + expectedZonesLen: 1, + expectedZones: []nbdns.CustomZone{ + { + Domain: "netbird.cloud.", + Records: []nbdns.SimpleRecord{ + { + Name: "peer-a.netbird.cloud.", + Type: 1, + Class: nbdns.DefaultClass, + TTL: 300, + RData: "100.64.0.1", + }, + }, + }, + }, + expectedNSGroupsLen: 1, + expectedNSGroups: []*nbdns.NameServerGroup{ + { + Primary: true, + NameServers: []nbdns.NameServer{ + { + IP: netip.MustParseAddr("8.8.8.8"), + NSType: 1, + Port: 53, + }, + }, + }, + }, + expectedSerial: 1, + }, + { + name: "Empty DNS Config Should Be OK", + networkMap: &mgmtProto.NetworkMap{ + Serial: 1, + PeerConfig: nil, + RemotePeersIsEmpty: false, + Routes: nil, + DNSConfig: nil, + }, + expectedZonesLen: 0, + expectedZones: []nbdns.CustomZone{}, + expectedNSGroupsLen: 0, + expectedNSGroups: []*nbdns.NameServerGroup{}, + expectedSerial: 1, + }, + { + name: "Error Shouldn't Break Engine", + inputErr: fmt.Errorf("mocking error"), + networkMap: &mgmtProto.NetworkMap{ + Serial: 1, + PeerConfig: nil, + RemotePeersIsEmpty: false, + Routes: nil, + }, + expectedZonesLen: 0, + expectedZones: []nbdns.CustomZone{}, + expectedNSGroupsLen: 0, + expectedNSGroups: []*nbdns.NameServerGroup{}, + expectedSerial: 1, + }, + } + + for n, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + // test setup + key, err := wgtypes.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + return + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + wgIfaceName := fmt.Sprintf("utun%d", 104+n) + wgAddr := fmt.Sprintf("100.66.%d.1/24", n) + + engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{ + WgIfaceName: wgIfaceName, + WgAddr: wgAddr, + WgPrivateKey: key, + WgPort: 33100, + }, nbstatus.NewRecorder()) + engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, iface.DefaultMTU) + assert.NoError(t, err, "shouldn't return error") + + mockRouteManager := &routemanager.MockManager{ + UpdateRoutesFunc: func(updateSerial uint64, newRoutes []*route.Route) error { + return nil + }, + } + + engine.routeManager = mockRouteManager + + input := struct { + inputSerial uint64 + inputNSGroups []*nbdns.NameServerGroup + inputZones []nbdns.CustomZone + }{} + + mockDNSServer := &dns.MockServer{ + UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { + input.inputSerial = serial + input.inputZones = update.CustomZones + input.inputNSGroups = update.NameServerGroups + return testCase.inputErr + }, + } + + engine.dnsServer = mockDNSServer + + defer func() { + exitErr := engine.Stop() + if exitErr != nil { + return + } + }() + + err = engine.updateNetworkMap(testCase.networkMap) + assert.NoError(t, err, "shouldn't return error") + assert.Equal(t, testCase.expectedSerial, input.inputSerial, "serial should match") + assert.Len(t, input.inputNSGroups, testCase.expectedZonesLen, "zones len should match") + assert.Equal(t, testCase.expectedZones, input.inputZones, "custom zones should match") + assert.Len(t, input.inputNSGroups, testCase.expectedNSGroupsLen, "ns groups len should match") + assert.Equal(t, testCase.expectedNSGroups, input.inputNSGroups, "ns groups should match") + }) + } +} + func TestEngine_MultiplePeers(t *testing.T) { // log.SetLevel(log.DebugLevel) @@ -761,7 +940,7 @@ func startManagement(port int, dataDir string) (*grpc.Server, error) { log.Fatalf("failed creating a store: %s: %v", config.Datadir, err) } peersUpdateManager := server.NewPeersUpdateManager() - accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "") + accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "") if err != nil { return nil, err } diff --git a/dns/dns.go b/dns/dns.go index 5c9144375..a09e4b5df 100644 --- a/dns/dns.go +++ b/dns/dns.go @@ -5,6 +5,9 @@ package dns import ( "fmt" "github.com/miekg/dns" + "golang.org/x/net/idna" + "regexp" + "strings" ) const ( @@ -16,12 +19,14 @@ const ( DefaultClass = "IN" ) -// Update represents a dns update that is exchanged between management and peers -type Update struct { +const invalidHostLabel = "[^a-zA-Z0-9-]+" + +// Config represents a dns configuration that is exchanged between management and peers +type Config struct { // ServiceEnable indicates if the service should be enabled ServiceEnable bool // NameServerGroups contains a list of nameserver group - NameServerGroups []NameServerGroup + NameServerGroups []*NameServerGroup // CustomZones contains a list of custom zone CustomZones []CustomZone } @@ -54,3 +59,26 @@ func (s SimpleRecord) String() string { fqdn := dns.Fqdn(s.Name) return fmt.Sprintf("%s %d %s %s %s", fqdn, s.TTL, s.Class, dns.Type(s.Type).String(), s.RData) } + +// GetParsedDomainLabel returns a domain label with max 59 characters, +// parsed for old Hosts.txt requirements, and converted to ASCII and lowercase +func GetParsedDomainLabel(name string) (string, error) { + labels := dns.SplitDomainName(name) + if len(labels) == 0 { + return "", fmt.Errorf("got empty label list for name \"%s\"", name) + } + rawLabel := labels[0] + ascii, err := idna.Punycode.ToASCII(rawLabel) + if err != nil { + return "", fmt.Errorf("unable to convert host lavel to ASCII, error: %v", err) + } + + invalidHostMatcher := regexp.MustCompile(invalidHostLabel) + + validHost := strings.ToLower(invalidHostMatcher.ReplaceAllString(ascii, "-")) + if len(validHost) > 58 { + validHost = validHost[:59] + } + + return validHost, nil +} diff --git a/management/client/client_test.go b/management/client/client_test.go index 48e1f2be8..c48f151b5 100644 --- a/management/client/client_test.go +++ b/management/client/client_test.go @@ -55,7 +55,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) { } peersUpdateManager := mgmt.NewPeersUpdateManager() - accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "") + accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "") if err != nil { t.Fatal(err) } diff --git a/management/cmd/defaults.go b/management/cmd/defaults.go new file mode 100644 index 000000000..424a29507 --- /dev/null +++ b/management/cmd/defaults.go @@ -0,0 +1,18 @@ +package cmd + +const ( + defaultMgmtDataDir = "/var/lib/netbird/" + defaultMgmtConfigDir = "/etc/netbird" + defaultLogDir = "/var/log/netbird" + + oldDefaultMgmtDataDir = "/var/lib/wiretrustee/" + oldDefaultMgmtConfigDir = "/etc/wiretrustee" + oldDefaultLogDir = "/var/log/wiretrustee" + + defaultMgmtConfig = defaultMgmtConfigDir + "/management.json" + defaultLogFile = defaultLogDir + "/management.log" + oldDefaultMgmtConfig = oldDefaultMgmtConfigDir + "/management.json" + oldDefaultLogFile = oldDefaultLogDir + "/management.log" + + defaultSingleAccModeDomain = "netbird.selfhosted" +) diff --git a/management/cmd/management.go b/management/cmd/management.go index 40953df25..bcd8252a8 100644 --- a/management/cmd/management.go +++ b/management/cmd/management.go @@ -8,6 +8,7 @@ import ( "flag" "fmt" "github.com/google/uuid" + "github.com/miekg/dns" httpapi "github.com/netbirdio/netbird/management/server/http" "github.com/netbirdio/netbird/management/server/metrics" "github.com/netbirdio/netbird/management/server/telemetry" @@ -89,6 +90,11 @@ var ( } } + _, valid := dns.IsDomainName(dnsDomain) + if !valid || len(dnsDomain) > 192 { + return fmt.Errorf("failed parsing the provided dns-domain. Valid status: %t, Lenght: %d", valid, len(dnsDomain)) + } + return nil }, RunE: func(cmd *cobra.Command, args []string) error { @@ -136,7 +142,7 @@ var ( if disableSingleAccMode { mgmtSingleAccModeDomain = "" } - accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain) + accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain, dnsDomain) if err != nil { return fmt.Errorf("failed to build default manager: %v", err) } diff --git a/management/cmd/root.go b/management/cmd/root.go index 84e2eebeb..74a3d3ffd 100644 --- a/management/cmd/root.go +++ b/management/cmd/root.go @@ -13,23 +13,13 @@ const ( ) var ( - defaultMgmtConfigDir string - defaultMgmtDataDir string - defaultMgmtConfig string - defaultSingleAccModeDomain string - defaultLogDir string - defaultLogFile string - oldDefaultMgmtConfigDir string - oldDefaultMgmtDataDir string - oldDefaultMgmtConfig string - oldDefaultLogDir string - oldDefaultLogFile string - mgmtDataDir string - mgmtConfig string - logLevel string - logFile string - disableMetrics bool - disableSingleAccMode bool + dnsDomain string + mgmtDataDir string + mgmtConfig string + logLevel string + logFile string + disableMetrics bool + disableSingleAccMode bool rootCmd = &cobra.Command{ Use: "netbird-mgmt", @@ -48,22 +38,6 @@ func Execute() error { func init() { stopCh = make(chan int) - - defaultMgmtDataDir = "/var/lib/netbird/" - defaultSingleAccModeDomain = "netbird.selfhosted" - defaultMgmtConfigDir = "/etc/netbird" - defaultLogDir = "/var/log/netbird" - - oldDefaultMgmtDataDir = "/var/lib/wiretrustee/" - oldDefaultMgmtConfigDir = "/etc/wiretrustee" - oldDefaultLogDir = "/var/log/wiretrustee" - - defaultMgmtConfig = defaultMgmtConfigDir + "/management.json" - defaultLogFile = defaultLogDir + "/management.log" - - oldDefaultMgmtConfig = oldDefaultMgmtConfigDir + "/management.json" - oldDefaultLogFile = oldDefaultLogDir + "/management.log" - mgmtCmd.Flags().IntVar(&mgmtPort, "port", 80, "server port to listen on (defaults to 443 if TLS is enabled, 80 otherwise") mgmtCmd.Flags().IntVar(&mgmtMetricsPort, "metrics-port", 8081, "metrics endpoint http port. Metrics are accessible under host:metrics-port/metrics") mgmtCmd.Flags().StringVar(&mgmtDataDir, "datadir", defaultMgmtDataDir, "server data directory location") @@ -74,6 +48,7 @@ func init() { mgmtCmd.Flags().StringVar(&certFile, "cert-file", "", "Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect") mgmtCmd.Flags().StringVar(&certKey, "cert-key", "", "Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect") mgmtCmd.Flags().BoolVar(&disableMetrics, "disable-anonymous-metrics", false, "disables push of anonymous usage metrics to NetBird") + mgmtCmd.Flags().StringVar(&dnsDomain, "dns-domain", defaultSingleAccModeDomain, fmt.Sprintf("Domain used for peer resolution. This is appended to the peer's name, e.g. pi-server. %s. Max lenght is 192 characters to allow appending to a peer name with up to 63 characters.", defaultSingleAccModeDomain)) rootCmd.MarkFlagRequired("config") //nolint rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "") diff --git a/management/proto/management.pb.go b/management/proto/management.pb.go index 89bdcf0bd..9e9f7e8c9 100644 --- a/management/proto/management.pb.go +++ b/management/proto/management.pb.go @@ -1,15 +1,15 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v3.12.4 +// protoc v3.21.9 // source: management.proto package proto import ( - timestamp "github.com/golang/protobuf/ptypes/timestamp" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) @@ -611,7 +611,7 @@ type ServerKeyResponse struct { // Server's Wireguard public key Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // Key expiration timestamp after which the key should be fetched again by the client - ExpiresAt *timestamp.Timestamp `protobuf:"bytes,2,opt,name=expiresAt,proto3" json:"expiresAt,omitempty"` + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expiresAt,proto3" json:"expiresAt,omitempty"` // Version of the Wiretrustee Management Service protocol Version int32 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"` } @@ -655,7 +655,7 @@ func (x *ServerKeyResponse) GetKey() string { return "" } -func (x *ServerKeyResponse) GetExpiresAt() *timestamp.Timestamp { +func (x *ServerKeyResponse) GetExpiresAt() *timestamppb.Timestamp { if x != nil { return x.ExpiresAt } @@ -982,6 +982,8 @@ type NetworkMap struct { RemotePeersIsEmpty bool `protobuf:"varint,4,opt,name=remotePeersIsEmpty,proto3" json:"remotePeersIsEmpty,omitempty"` // List of routes to be applied Routes []*Route `protobuf:"bytes,5,rep,name=Routes,proto3" json:"Routes,omitempty"` + // DNS config to be applied + DNSConfig *DNSConfig `protobuf:"bytes,6,opt,name=DNSConfig,proto3" json:"DNSConfig,omitempty"` } func (x *NetworkMap) Reset() { @@ -1051,6 +1053,13 @@ func (x *NetworkMap) GetRoutes() []*Route { return nil } +func (x *NetworkMap) GetDNSConfig() *DNSConfig { + if x != nil { + return x.DNSConfig + } + return nil +} + // RemotePeerConfig represents a configuration of a remote peer. // The properties are used to configure Wireguard Peers sections type RemotePeerConfig struct { @@ -1467,6 +1476,334 @@ func (x *Route) GetNetID() string { return "" } +// DNSConfig represents a dns.Update +type DNSConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ServiceEnable bool `protobuf:"varint,1,opt,name=ServiceEnable,proto3" json:"ServiceEnable,omitempty"` + NameServerGroups []*NameServerGroup `protobuf:"bytes,2,rep,name=NameServerGroups,proto3" json:"NameServerGroups,omitempty"` + CustomZones []*CustomZone `protobuf:"bytes,3,rep,name=CustomZones,proto3" json:"CustomZones,omitempty"` +} + +func (x *DNSConfig) Reset() { + *x = DNSConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_management_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DNSConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DNSConfig) ProtoMessage() {} + +func (x *DNSConfig) ProtoReflect() protoreflect.Message { + mi := &file_management_proto_msgTypes[20] + 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 DNSConfig.ProtoReflect.Descriptor instead. +func (*DNSConfig) Descriptor() ([]byte, []int) { + return file_management_proto_rawDescGZIP(), []int{20} +} + +func (x *DNSConfig) GetServiceEnable() bool { + if x != nil { + return x.ServiceEnable + } + return false +} + +func (x *DNSConfig) GetNameServerGroups() []*NameServerGroup { + if x != nil { + return x.NameServerGroups + } + return nil +} + +func (x *DNSConfig) GetCustomZones() []*CustomZone { + if x != nil { + return x.CustomZones + } + return nil +} + +// CustomZone represents a dns.CustomZone +type CustomZone struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Domain string `protobuf:"bytes,1,opt,name=Domain,proto3" json:"Domain,omitempty"` + Records []*SimpleRecord `protobuf:"bytes,2,rep,name=Records,proto3" json:"Records,omitempty"` +} + +func (x *CustomZone) Reset() { + *x = CustomZone{} + if protoimpl.UnsafeEnabled { + mi := &file_management_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CustomZone) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CustomZone) ProtoMessage() {} + +func (x *CustomZone) ProtoReflect() protoreflect.Message { + mi := &file_management_proto_msgTypes[21] + 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 CustomZone.ProtoReflect.Descriptor instead. +func (*CustomZone) Descriptor() ([]byte, []int) { + return file_management_proto_rawDescGZIP(), []int{21} +} + +func (x *CustomZone) GetDomain() string { + if x != nil { + return x.Domain + } + return "" +} + +func (x *CustomZone) GetRecords() []*SimpleRecord { + if x != nil { + return x.Records + } + return nil +} + +// SimpleRecord represents a dns.SimpleRecord +type SimpleRecord struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"` + Type int64 `protobuf:"varint,2,opt,name=Type,proto3" json:"Type,omitempty"` + Class string `protobuf:"bytes,3,opt,name=Class,proto3" json:"Class,omitempty"` + TTL int64 `protobuf:"varint,4,opt,name=TTL,proto3" json:"TTL,omitempty"` + RData string `protobuf:"bytes,5,opt,name=RData,proto3" json:"RData,omitempty"` +} + +func (x *SimpleRecord) Reset() { + *x = SimpleRecord{} + if protoimpl.UnsafeEnabled { + mi := &file_management_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SimpleRecord) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SimpleRecord) ProtoMessage() {} + +func (x *SimpleRecord) ProtoReflect() protoreflect.Message { + mi := &file_management_proto_msgTypes[22] + 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 SimpleRecord.ProtoReflect.Descriptor instead. +func (*SimpleRecord) Descriptor() ([]byte, []int) { + return file_management_proto_rawDescGZIP(), []int{22} +} + +func (x *SimpleRecord) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SimpleRecord) GetType() int64 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *SimpleRecord) GetClass() string { + if x != nil { + return x.Class + } + return "" +} + +func (x *SimpleRecord) GetTTL() int64 { + if x != nil { + return x.TTL + } + return 0 +} + +func (x *SimpleRecord) GetRData() string { + if x != nil { + return x.RData + } + return "" +} + +// NameServerGroup represents a dns.NameServerGroup +type NameServerGroup struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NameServers []*NameServer `protobuf:"bytes,1,rep,name=NameServers,proto3" json:"NameServers,omitempty"` + Primary bool `protobuf:"varint,2,opt,name=Primary,proto3" json:"Primary,omitempty"` + Domains []string `protobuf:"bytes,3,rep,name=Domains,proto3" json:"Domains,omitempty"` +} + +func (x *NameServerGroup) Reset() { + *x = NameServerGroup{} + if protoimpl.UnsafeEnabled { + mi := &file_management_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NameServerGroup) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NameServerGroup) ProtoMessage() {} + +func (x *NameServerGroup) ProtoReflect() protoreflect.Message { + mi := &file_management_proto_msgTypes[23] + 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 NameServerGroup.ProtoReflect.Descriptor instead. +func (*NameServerGroup) Descriptor() ([]byte, []int) { + return file_management_proto_rawDescGZIP(), []int{23} +} + +func (x *NameServerGroup) GetNameServers() []*NameServer { + if x != nil { + return x.NameServers + } + return nil +} + +func (x *NameServerGroup) GetPrimary() bool { + if x != nil { + return x.Primary + } + return false +} + +func (x *NameServerGroup) GetDomains() []string { + if x != nil { + return x.Domains + } + return nil +} + +// NameServer represents a dns.NameServer +type NameServer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IP string `protobuf:"bytes,1,opt,name=IP,proto3" json:"IP,omitempty"` + NSType int64 `protobuf:"varint,2,opt,name=NSType,proto3" json:"NSType,omitempty"` + Port int64 `protobuf:"varint,3,opt,name=Port,proto3" json:"Port,omitempty"` +} + +func (x *NameServer) Reset() { + *x = NameServer{} + if protoimpl.UnsafeEnabled { + mi := &file_management_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NameServer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NameServer) ProtoMessage() {} + +func (x *NameServer) ProtoReflect() protoreflect.Message { + mi := &file_management_proto_msgTypes[24] + 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 NameServer.ProtoReflect.Descriptor instead. +func (*NameServer) Descriptor() ([]byte, []int) { + return file_management_proto_rawDescGZIP(), []int{24} +} + +func (x *NameServer) GetIP() string { + if x != nil { + return x.IP + } + return "" +} + +func (x *NameServer) GetNSType() int64 { + if x != nil { + return x.NSType + } + return 0 +} + +func (x *NameServer) GetPort() int64 { + if x != nil { + return x.Port + } + return 0 +} + var File_management_proto protoreflect.FileDescriptor var file_management_proto_rawDesc = []byte{ @@ -1583,7 +1920,7 @@ var file_management_proto_rawDesc = []byte{ 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, 0x22, 0xf7, 0x01, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, + 0x66, 0x69, 0x67, 0x22, 0xac, 0x02, 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, @@ -1598,85 +1935,125 @@ var file_management_proto_rawDesc = []byte{ 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, 0x22, 0x83, 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, 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, 0xda, 0x01, 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, 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, 0x32, 0xf7, 0x02, 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, 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, + 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, 0x22, 0x83, 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, 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, 0xda, 0x01, 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, 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, 0x7f, 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, 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, 0x32, 0xf7, + 0x02, 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, 0x22, - 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 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, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1692,7 +2069,7 @@ func file_management_proto_rawDescGZIP() []byte { } var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 20) +var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 25) var file_management_proto_goTypes = []interface{}{ (HostConfig_Protocol)(0), // 0: management.HostConfig.Protocol (DeviceAuthorizationFlowProvider)(0), // 1: management.DeviceAuthorizationFlow.provider @@ -1716,7 +2093,12 @@ var file_management_proto_goTypes = []interface{}{ (*DeviceAuthorizationFlow)(nil), // 19: management.DeviceAuthorizationFlow (*ProviderConfig)(nil), // 20: management.ProviderConfig (*Route)(nil), // 21: management.Route - (*timestamp.Timestamp)(nil), // 22: google.protobuf.Timestamp + (*DNSConfig)(nil), // 22: management.DNSConfig + (*CustomZone)(nil), // 23: management.CustomZone + (*SimpleRecord)(nil), // 24: management.SimpleRecord + (*NameServerGroup)(nil), // 25: management.NameServerGroup + (*NameServer)(nil), // 26: management.NameServer + (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp } var file_management_proto_depIdxs = []int32{ 11, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig @@ -1727,7 +2109,7 @@ var file_management_proto_depIdxs = []int32{ 6, // 5: management.LoginRequest.peerKeys:type_name -> management.PeerKeys 11, // 6: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig 14, // 7: management.LoginResponse.peerConfig:type_name -> management.PeerConfig - 22, // 8: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp + 27, // 8: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp 12, // 9: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig 13, // 10: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig 12, // 11: management.WiretrusteeConfig.signal:type_name -> management.HostConfig @@ -1737,24 +2119,29 @@ var file_management_proto_depIdxs = []int32{ 14, // 15: management.NetworkMap.peerConfig:type_name -> management.PeerConfig 16, // 16: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig 21, // 17: management.NetworkMap.Routes:type_name -> management.Route - 17, // 18: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig - 1, // 19: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider - 20, // 20: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig - 2, // 21: management.ManagementService.Login:input_type -> management.EncryptedMessage - 2, // 22: management.ManagementService.Sync:input_type -> management.EncryptedMessage - 10, // 23: management.ManagementService.GetServerKey:input_type -> management.Empty - 10, // 24: management.ManagementService.isHealthy:input_type -> management.Empty - 2, // 25: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage - 2, // 26: management.ManagementService.Login:output_type -> management.EncryptedMessage - 2, // 27: management.ManagementService.Sync:output_type -> management.EncryptedMessage - 9, // 28: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse - 10, // 29: management.ManagementService.isHealthy:output_type -> management.Empty - 2, // 30: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage - 26, // [26:31] is the sub-list for method output_type - 21, // [21:26] is the sub-list for method input_type - 21, // [21:21] is the sub-list for extension type_name - 21, // [21:21] is the sub-list for extension extendee - 0, // [0:21] is the sub-list for field type_name + 22, // 18: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig + 17, // 19: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig + 1, // 20: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider + 20, // 21: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig + 25, // 22: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup + 23, // 23: management.DNSConfig.CustomZones:type_name -> management.CustomZone + 24, // 24: management.CustomZone.Records:type_name -> management.SimpleRecord + 26, // 25: management.NameServerGroup.NameServers:type_name -> management.NameServer + 2, // 26: management.ManagementService.Login:input_type -> management.EncryptedMessage + 2, // 27: management.ManagementService.Sync:input_type -> management.EncryptedMessage + 10, // 28: management.ManagementService.GetServerKey:input_type -> management.Empty + 10, // 29: management.ManagementService.isHealthy:input_type -> management.Empty + 2, // 30: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage + 2, // 31: management.ManagementService.Login:output_type -> management.EncryptedMessage + 2, // 32: management.ManagementService.Sync:output_type -> management.EncryptedMessage + 9, // 33: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse + 10, // 34: management.ManagementService.isHealthy:output_type -> management.Empty + 2, // 35: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage + 31, // [31:36] is the sub-list for method output_type + 26, // [26:31] is the sub-list for method input_type + 26, // [26:26] is the sub-list for extension type_name + 26, // [26:26] is the sub-list for extension extendee + 0, // [0:26] is the sub-list for field type_name } func init() { file_management_proto_init() } @@ -2003,6 +2390,66 @@ func file_management_proto_init() { return nil } } + file_management_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DNSConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_management_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CustomZone); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_management_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SimpleRecord); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_management_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NameServerGroup); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_management_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NameServer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -2010,7 +2457,7 @@ func file_management_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_management_proto_rawDesc, NumEnums: 2, - NumMessages: 20, + NumMessages: 25, NumExtensions: 0, NumServices: 1, }, diff --git a/management/proto/management.proto b/management/proto/management.proto index b580c29ef..0b8eae9f0 100644 --- a/management/proto/management.proto +++ b/management/proto/management.proto @@ -178,6 +178,9 @@ message NetworkMap { // List of routes to be applied repeated Route Routes = 5; + + // DNS config to be applied + DNSConfig DNSConfig = 6; } // RemotePeerConfig represents a configuration of a remote peer. @@ -246,4 +249,40 @@ message Route { int64 Metric = 5; bool Masquerade = 6; string NetID = 7; +} + +// DNSConfig represents a dns.Update +message DNSConfig { + bool ServiceEnable = 1; + repeated NameServerGroup NameServerGroups = 2; + repeated CustomZone CustomZones = 3; +} + +// CustomZone represents a dns.CustomZone +message CustomZone { + string Domain = 1; + repeated SimpleRecord Records = 2; +} + +// SimpleRecord represents a dns.SimpleRecord +message SimpleRecord { + string Name = 1; + int64 Type = 2; + string Class = 3; + int64 TTL = 4; + string RData = 5; +} + +// NameServerGroup represents a dns.NameServerGroup +message NameServerGroup { + repeated NameServer NameServers = 1; + bool Primary = 2; + repeated string Domains = 3; +} + +// NameServer represents a dns.NameServer +message NameServer { + string IP = 1; + int64 NSType = 2; + int64 Port = 3; } \ No newline at end of file diff --git a/management/server/account.go b/management/server/account.go index 3183de0a0..9ceff43a9 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -114,6 +114,8 @@ type DefaultAccountManager struct { singleAccountMode bool // singleAccountModeDomain is a domain to use in singleAccountMode setup singleAccountModeDomain string + // dnsDomain is used for peer resolution. This is appended to the peer's name + dnsDomain string } // Account represents a unique account of the system @@ -279,6 +281,17 @@ func (a *Account) FindUser(userID string) (*User, error) { return user, nil } +// getPeerDNSLabels return the account's peers' dns labels +func (a *Account) getPeerDNSLabels() lookupMap { + existingLabels := make(lookupMap) + for _, peer := range a.Peers { + if peer.DNSLabel != "" { + existingLabels[peer.DNSLabel] = struct{}{} + } + } + return existingLabels +} + func (a *Account) Copy() *Account { peers := map[string]*Peer{} for id, peer := range a.Peers { @@ -343,7 +356,7 @@ func (a *Account) GetGroupAll() (*Group, error) { // BuildManager creates a new DefaultAccountManager with a provided Store func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager, - singleAccountModeDomain string) (*DefaultAccountManager, error) { + singleAccountModeDomain string, dnsDomain string) (*DefaultAccountManager, error) { am := &DefaultAccountManager{ Store: store, mux: sync.Mutex{}, @@ -352,6 +365,7 @@ func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManage ctx: context.Background(), cacheMux: sync.Mutex{}, cacheLoading: map[string]chan struct{}{}, + dnsDomain: dnsDomain, } allAccounts := store.GetAllAccounts() // enable single account mode only if configured by user and number of existing accounts is not grater than 1 @@ -367,10 +381,23 @@ func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManage // we create 'all' group and add all peers into it // also we create default rule with source as destination for _, account := range allAccounts { + shouldSave := false + _, err := account.GetGroupAll() if err != nil { addAllGroup(account) - if err := store.SaveAccount(account); err != nil { + shouldSave = true + } + + existingLabels := account.getPeerDNSLabels() + if len(existingLabels) != len(account.Peers) { + addPeerLabelsToAccount(account, existingLabels) + shouldSave = true + } + + if shouldSave { + err = store.SaveAccount(account) + if err != nil { return nil, err } } diff --git a/management/server/account_test.go b/management/server/account_test.go index 7213d5015..50294b326 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -1124,7 +1124,7 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) { if err != nil { return nil, err } - return BuildManager(store, NewPeersUpdateManager(), nil, "") + return BuildManager(store, NewPeersUpdateManager(), nil, "", "") } func createStore(t *testing.T) (Store, error) { diff --git a/management/server/dns.go b/management/server/dns.go new file mode 100644 index 000000000..60653df5b --- /dev/null +++ b/management/server/dns.go @@ -0,0 +1,152 @@ +package server + +import ( + "fmt" + "github.com/miekg/dns" + nbdns "github.com/netbirdio/netbird/dns" + "github.com/netbirdio/netbird/management/proto" + log "github.com/sirupsen/logrus" + "strconv" +) + +type lookupMap map[string]struct{} + +const defaultTTL = 300 + +func toProtocolDNSConfig(update nbdns.Config) *proto.DNSConfig { + protoUpdate := &proto.DNSConfig{ServiceEnable: update.ServiceEnable} + + for _, zone := range update.CustomZones { + protoZone := &proto.CustomZone{Domain: zone.Domain} + for _, record := range zone.Records { + protoZone.Records = append(protoZone.Records, &proto.SimpleRecord{ + Name: record.Name, + Type: int64(record.Type), + Class: record.Class, + TTL: int64(record.TTL), + RData: record.RData, + }) + } + protoUpdate.CustomZones = append(protoUpdate.CustomZones, protoZone) + } + + for _, nsGroup := range update.NameServerGroups { + protoGroup := &proto.NameServerGroup{ + Primary: nsGroup.Primary, + Domains: nsGroup.Domains, + } + for _, ns := range nsGroup.NameServers { + protoNS := &proto.NameServer{ + IP: ns.IP.String(), + Port: int64(ns.Port), + NSType: int64(ns.NSType), + } + protoGroup.NameServers = append(protoGroup.NameServers, protoNS) + } + protoUpdate.NameServerGroups = append(protoUpdate.NameServerGroups, protoGroup) + } + + return protoUpdate +} + +func getPeersCustomZone(account *Account, dnsDomain string) nbdns.CustomZone { + if dnsDomain == "" { + log.Errorf("no dns domain is set, returning empty zone") + return nbdns.CustomZone{} + } + + customZone := nbdns.CustomZone{ + Domain: dns.Fqdn(dnsDomain), + } + + for _, peer := range account.Peers { + if peer.DNSLabel == "" { + log.Errorf("found a peer with empty dns label. It was probably caused by a invalid character in its name. Peer Name: %s", peer.Name) + continue + } + + customZone.Records = append(customZone.Records, nbdns.SimpleRecord{ + Name: dns.Fqdn(peer.DNSLabel + "." + dnsDomain), + Type: int(dns.TypeA), + Class: nbdns.DefaultClass, + TTL: defaultTTL, + RData: peer.IP.String(), + }) + } + + return customZone +} + +func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup { + groupList := make(lookupMap) + for groupID, group := range account.Groups { + for _, id := range group.Peers { + if id == peerID { + groupList[groupID] = struct{}{} + break + } + } + } + + var peerNSGroups []*nbdns.NameServerGroup + + for _, nsGroup := range account.NameServerGroups { + if !nsGroup.Enabled { + continue + } + for _, gID := range nsGroup.Groups { + _, found := groupList[gID] + if found { + peerNSGroups = append(peerNSGroups, nsGroup.Copy()) + break + } + } + } + + return peerNSGroups +} + +func addPeerLabelsToAccount(account *Account, peerLabels lookupMap) { + for _, peer := range account.Peers { + label, err := getPeerHostLabel(peer.Name, peerLabels) + if err != nil { + log.Errorf("got an error while generating a peer host label. Peer name %s, error: %v. Trying with the peer's meta hostname", peer.Name, err) + label, err = getPeerHostLabel(peer.Meta.Hostname, peerLabels) + if err != nil { + log.Errorf("got another error while generating a peer host label with hostname. Peer hostname %s, error: %v. Skiping", peer.Meta.Hostname, err) + continue + } + } + peer.DNSLabel = label + peerLabels[label] = struct{}{} + } +} + +func getPeerHostLabel(name string, peerLabels lookupMap) (string, error) { + label, err := nbdns.GetParsedDomainLabel(name) + if err != nil { + return "", err + } + + uniqueLabel := getUniqueHostLabel(label, peerLabels) + if uniqueLabel == "" { + return "", fmt.Errorf("couldn't find a unique valid lavel for %s, parsed label %s", name, label) + } + return uniqueLabel, nil +} + +// getUniqueHostLabel look for a unique host label, and if doesn't find add a suffix up to 999 +func getUniqueHostLabel(name string, peerLabels lookupMap) string { + _, found := peerLabels[name] + if !found { + return name + } + for i := 1; i < 1000; i++ { + nameWithSuffix := name + "-" + strconv.Itoa(i) + _, found = peerLabels[nameWithSuffix] + if !found { + return nameWithSuffix + } + } + return "" +} diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index d7418fd49..6ba54add3 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "github.com/netbirdio/netbird/management/server/telemetry" - "github.com/netbirdio/netbird/route" gPeer "google.golang.org/grpc/peer" "strings" "time" @@ -253,17 +252,15 @@ func (s *GRPCServer) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) if err != nil { return nil, status.Errorf(codes.Internal, "unable to fetch network map after registering peer, error: %v", err) } - // notify other peers of our registration for _, remotePeer := range networkMap.Peers { - // exclude notified peer and add ourselves - peersToSend := []*Peer{peer} - for _, p := range networkMap.Peers { - if remotePeer.Key != p.Key { - peersToSend = append(peersToSend, p) - } + // todo update this once we have store v2 to avoid lock/peer + remotePeerNetworkMap, err := s.accountManager.GetNetworkMap(remotePeer.Key) + if err != nil { + return nil, status.Errorf(codes.Internal, "unable to fetch network map after registering peer, error: %v", err) } - update := toSyncResponse(s.config, remotePeer, peersToSend, networkMap.Routes, nil, networkMap.Network.CurrentSerial(), networkMap.Network) + + update := toSyncResponse(s.config, remotePeer, nil, remotePeerNetworkMap) err = s.peersUpdateManager.SendUpdate(remotePeer.Key, &UpdateMessage{Update: update}) if err != nil { // todo rethink if we should keep this return @@ -451,14 +448,16 @@ func toRemotePeerConfig(peers []*Peer) []*proto.RemotePeerConfig { return remotePeers } -func toSyncResponse(config *Config, peer *Peer, peers []*Peer, routes []*route.Route, turnCredentials *TURNCredentials, serial uint64, network *Network) *proto.SyncResponse { +func toSyncResponse(config *Config, peer *Peer, turnCredentials *TURNCredentials, networkMap *NetworkMap) *proto.SyncResponse { wtConfig := toWiretrusteeConfig(config, turnCredentials) - pConfig := toPeerConfig(peer, network) + pConfig := toPeerConfig(peer, networkMap.Network) - remotePeers := toRemotePeerConfig(peers) + remotePeers := toRemotePeerConfig(networkMap.Peers) - routesUpdate := toProtocolRoutes(routes) + routesUpdate := toProtocolRoutes(networkMap.Routes) + + dnsUpdate := toProtocolDNSConfig(networkMap.DNSConfig) return &proto.SyncResponse{ WiretrusteeConfig: wtConfig, @@ -466,11 +465,12 @@ func toSyncResponse(config *Config, peer *Peer, peers []*Peer, routes []*route.R RemotePeers: remotePeers, RemotePeersIsEmpty: len(remotePeers) == 0, NetworkMap: &proto.NetworkMap{ - Serial: serial, + Serial: networkMap.Network.CurrentSerial(), PeerConfig: pConfig, RemotePeers: remotePeers, RemotePeersIsEmpty: len(remotePeers) == 0, Routes: routesUpdate, + DNSConfig: dnsUpdate, }, } } @@ -496,7 +496,7 @@ func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto. } else { turnCredentials = nil } - plainResp := toSyncResponse(s.config, peer, networkMap.Peers, networkMap.Routes, turnCredentials, networkMap.Network.CurrentSerial(), networkMap.Network) + plainResp := toSyncResponse(s.config, peer, turnCredentials, networkMap) encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, plainResp) if err != nil { diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index ff285e7de..377b36937 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -136,6 +136,9 @@ components: ui_version: description: Peer's desktop UI version type: string + dns_label: + description: Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud + type: string required: - ip - connected @@ -145,6 +148,7 @@ components: - groups - ssh_enabled - hostname + - dns_label SetupKey: type: object properties: @@ -480,7 +484,7 @@ components: path: description: Nameserver group field to update in form / type: string - enum: [ "name","description","enabled","groups","nameservers" ] + enum: [ "name", "description", "enabled", "groups", "nameservers", "primary", "domains" ] required: - path diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index 352e8717a..9b178f4f5 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -39,10 +39,12 @@ const ( // Defines values for NameserverGroupPatchOperationPath. const ( NameserverGroupPatchOperationPathDescription NameserverGroupPatchOperationPath = "description" + NameserverGroupPatchOperationPathDomains NameserverGroupPatchOperationPath = "domains" NameserverGroupPatchOperationPathEnabled NameserverGroupPatchOperationPath = "enabled" NameserverGroupPatchOperationPathGroups NameserverGroupPatchOperationPath = "groups" NameserverGroupPatchOperationPathName NameserverGroupPatchOperationPath = "name" NameserverGroupPatchOperationPathNameservers NameserverGroupPatchOperationPath = "nameservers" + NameserverGroupPatchOperationPathPrimary NameserverGroupPatchOperationPath = "primary" ) // Defines values for PatchMinimumOp. @@ -240,6 +242,9 @@ type Peer struct { // Connected Peer to Management connection status Connected bool `json:"connected"` + // DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud + DnsLabel string `json:"dns_label"` + // Groups Groups that the peer belongs to Groups []GroupMinimum `json:"groups"` diff --git a/management/server/http/nameservers.go b/management/server/http/nameservers.go index 55ac13ddf..bfef14f02 100644 --- a/management/server/http/nameservers.go +++ b/management/server/http/nameservers.go @@ -113,6 +113,8 @@ func (h *Nameservers) UpdateNameserverGroupHandler(w http.ResponseWriter, r *htt ID: nsGroupID, Name: req.Name, Description: req.Description, + Primary: req.Primary, + Domains: req.Domains, NameServers: nsList, Groups: req.Groups, Enabled: req.Enabled, @@ -168,6 +170,16 @@ func (h *Nameservers) PatchNameserverGroupHandler(w http.ResponseWriter, r *http Type: server.UpdateNameServerGroupDescription, Values: patch.Value, }) + case api.NameserverGroupPatchOperationPathPrimary: + operations = append(operations, server.NameServerGroupUpdateOperation{ + Type: server.UpdateNameServerGroupPrimary, + Values: patch.Value, + }) + case api.NameserverGroupPatchOperationPathDomains: + operations = append(operations, server.NameServerGroupUpdateOperation{ + Type: server.UpdateNameServerGroupDomains, + Values: patch.Value, + }) case api.NameserverGroupPatchOperationPathNameservers: operations = append(operations, server.NameServerGroupUpdateOperation{ Type: server.UpdateNameServerGroupNameServers, @@ -279,6 +291,8 @@ func toNameserverGroupResponse(serverNSGroup *nbdns.NameServerGroup) *api.Namese Id: serverNSGroup.ID, Name: serverNSGroup.Name, Description: serverNSGroup.Description, + Primary: serverNSGroup.Primary, + Domains: serverNSGroup.Domains, Groups: serverNSGroup.Groups, Nameservers: nsList, Enabled: serverNSGroup.Enabled, diff --git a/management/server/http/nameservers_test.go b/management/server/http/nameservers_test.go index 5e78e0624..b168a6ee8 100644 --- a/management/server/http/nameservers_test.go +++ b/management/server/http/nameservers_test.go @@ -172,6 +172,7 @@ func TestNameserversHandlers(t *testing.T) { }, Groups: []string{"group"}, Enabled: true, + Primary: true, }, }, { @@ -204,6 +205,7 @@ func TestNameserversHandlers(t *testing.T) { }, Groups: []string{"group"}, Enabled: true, + Primary: true, }, }, { @@ -238,6 +240,7 @@ func TestNameserversHandlers(t *testing.T) { Nameservers: toNameserverGroupResponse(baseExistingNSGroup).Nameservers, Groups: baseExistingNSGroup.Groups, Enabled: baseExistingNSGroup.Enabled, + Primary: baseExistingNSGroup.Primary, }, }, { diff --git a/management/server/http/peers.go b/management/server/http/peers.go index 2bd7e7243..1a7049e01 100644 --- a/management/server/http/peers.go +++ b/management/server/http/peers.go @@ -152,5 +152,6 @@ func toPeerResponse(peer *server.Peer, account *server.Account) *api.Peer { Hostname: peer.Meta.Hostname, UserId: &peer.UserID, UiVersion: &peer.Meta.UIVersion, + DnsLabel: peer.DNSLabel, } } diff --git a/management/server/management_proto_test.go b/management/server/management_proto_test.go index 74fb0181b..9ccabf6c4 100644 --- a/management/server/management_proto_test.go +++ b/management/server/management_proto_test.go @@ -403,7 +403,7 @@ func startManagement(t *testing.T, port int, config *Config) (*grpc.Server, erro return nil, err } peersUpdateManager := NewPeersUpdateManager() - accountManager, err := BuildManager(store, peersUpdateManager, nil, "") + accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "") if err != nil { return nil, err } diff --git a/management/server/management_test.go b/management/server/management_test.go index c71dc16ca..2e0d003c4 100644 --- a/management/server/management_test.go +++ b/management/server/management_test.go @@ -493,7 +493,7 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) { log.Fatalf("failed creating a store: %s: %v", config.Datadir, err) } peersUpdateManager := server.NewPeersUpdateManager() - accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "") + accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "") if err != nil { log.Fatalf("failed creating a manager: %v", err) } diff --git a/management/server/nameserver.go b/management/server/nameserver.go index 476a9a8e6..693e0af4d 100644 --- a/management/server/nameserver.go +++ b/management/server/nameserver.go @@ -4,6 +4,7 @@ import ( "github.com/miekg/dns" nbdns "github.com/netbirdio/netbird/dns" "github.com/rs/xid" + log "github.com/sirupsen/logrus" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "strconv" @@ -113,6 +114,12 @@ func (am *DefaultAccountManager) CreateNameServerGroup(accountID string, name, d return nil, err } + err = am.updateAccountPeers(account) + if err != nil { + log.Error(err) + return newNSGroup.Copy(), status.Errorf(codes.Unavailable, "failed to update peers after create nameserver %s", name) + } + return newNSGroup.Copy(), nil } @@ -143,6 +150,12 @@ func (am *DefaultAccountManager) SaveNameServerGroup(accountID string, nsGroupTo return err } + err = am.updateAccountPeers(account) + if err != nil { + log.Error(err) + return status.Errorf(codes.Unavailable, "failed to update peers after update nameserver %s", nsGroupToSave.Name) + } + return nil } @@ -239,6 +252,12 @@ func (am *DefaultAccountManager) UpdateNameServerGroup(accountID, nsGroupID stri return nil, err } + err = am.updateAccountPeers(account) + if err != nil { + log.Error(err) + return newNSGroup.Copy(), status.Errorf(codes.Unavailable, "failed to update peers after update nameserver %s", newNSGroup.Name) + } + return newNSGroup.Copy(), nil } @@ -260,6 +279,12 @@ func (am *DefaultAccountManager) DeleteNameServerGroup(accountID, nsGroupID stri return err } + err = am.updateAccountPeers(account) + if err != nil { + log.Error(err) + return status.Errorf(codes.Unavailable, "failed to update peers after deleting nameserver %s", nsGroupID) + } + return nil } diff --git a/management/server/nameserver_test.go b/management/server/nameserver_test.go index 462d9d20f..d672b4b2a 100644 --- a/management/server/nameserver_test.go +++ b/management/server/nameserver_test.go @@ -471,7 +471,7 @@ func TestSaveNameServerGroup(t *testing.T) { expectedNSGroup *nbdns.NameServerGroup }{ { - name: "Should Update Name Server Group", + name: "Should Config Name Server Group", existingNSGroup: existingNSGroup, newName: &validName, newGroups: validGroups, @@ -492,77 +492,77 @@ func TestSaveNameServerGroup(t *testing.T) { }, }, { - name: "Should Not Update If Name Is Small", + name: "Should Not Config If Name Is Small", existingNSGroup: existingNSGroup, newName: &invalidNameSmall, errFunc: require.Error, shouldCreate: false, }, { - name: "Should Not Update If Name Is Large", + name: "Should Not Config If Name Is Large", existingNSGroup: existingNSGroup, newName: &invalidNameLarge, errFunc: require.Error, shouldCreate: false, }, { - name: "Should Not Update If Name Exists", + name: "Should Not Config If Name Exists", existingNSGroup: existingNSGroup, newName: &invalidNameExisting, errFunc: require.Error, shouldCreate: false, }, { - name: "Should Not Update If ID Don't Exist", + name: "Should Not Config If ID Don't Exist", existingNSGroup: existingNSGroup, newID: &invalidID, errFunc: require.Error, shouldCreate: false, }, { - name: "Should Not Update If Nameserver List Is Small", + name: "Should Not Config If Nameserver List Is Small", existingNSGroup: existingNSGroup, newNSList: []nbdns.NameServer{}, errFunc: require.Error, shouldCreate: false, }, { - name: "Should Not Update If Nameserver List Is Large", + name: "Should Not Config If Nameserver List Is Large", existingNSGroup: existingNSGroup, newNSList: invalidNameServerListLarge, errFunc: require.Error, shouldCreate: false, }, { - name: "Should Not Update If Groups List Is Empty", + name: "Should Not Config If Groups List Is Empty", existingNSGroup: existingNSGroup, newGroups: []string{}, errFunc: require.Error, shouldCreate: false, }, { - name: "Should Not Update If Groups List Has Empty ID", + name: "Should Not Config If Groups List Has Empty ID", existingNSGroup: existingNSGroup, newGroups: []string{""}, errFunc: require.Error, shouldCreate: false, }, { - name: "Should Not Update If Groups List Has Non Existing Group ID", + name: "Should Not Config If Groups List Has Non Existing Group ID", existingNSGroup: existingNSGroup, newGroups: invalidGroups, errFunc: require.Error, shouldCreate: false, }, { - name: "Should Not Update If Domains List Is Empty", + name: "Should Not Config If Domains List Is Empty", existingNSGroup: existingNSGroup, newPrimary: &disabledPrimary, errFunc: require.Error, shouldCreate: false, }, { - name: "Should Not Update If Primary And Domains", + name: "Should Not Config If Primary And Domains", existingNSGroup: existingNSGroup, newPrimary: &existingNSGroup.Primary, newDomains: validDomains, @@ -570,7 +570,7 @@ func TestSaveNameServerGroup(t *testing.T) { shouldCreate: false, }, { - name: "Should Not Update If Domains List Is Invalid", + name: "Should Not Config If Domains List Is Invalid", existingNSGroup: existingNSGroup, newPrimary: &disabledPrimary, newDomains: invalidDomains, @@ -685,7 +685,7 @@ func TestUpdateNameServerGroup(t *testing.T) { expectedNSGroup *nbdns.NameServerGroup }{ { - name: "Should Update Single Property", + name: "Should Config Single Property", existingNSGroup: existingNSGroup, nsGroupID: existingNSGroup.ID, operations: []NameServerGroupUpdateOperation{ @@ -718,7 +718,7 @@ func TestUpdateNameServerGroup(t *testing.T) { }, }, { - name: "Should Update Multiple Properties", + name: "Should Config Multiple Properties", existingNSGroup: existingNSGroup, nsGroupID: existingNSGroup.ID, operations: []NameServerGroupUpdateOperation{ @@ -776,20 +776,20 @@ func TestUpdateNameServerGroup(t *testing.T) { }, }, { - name: "Should Not Update On Invalid ID", + name: "Should Not Config On Invalid ID", existingNSGroup: existingNSGroup, nsGroupID: "nonExistingNSGroup", errFunc: require.Error, }, { - name: "Should Not Update On Empty Operations", + name: "Should Not Config On Empty Operations", existingNSGroup: existingNSGroup, nsGroupID: existingNSGroup.ID, operations: []NameServerGroupUpdateOperation{}, errFunc: require.Error, }, { - name: "Should Not Update On Empty Values", + name: "Should Not Config On Empty Values", existingNSGroup: existingNSGroup, nsGroupID: existingNSGroup.ID, operations: []NameServerGroupUpdateOperation{ @@ -800,7 +800,7 @@ func TestUpdateNameServerGroup(t *testing.T) { errFunc: require.Error, }, { - name: "Should Not Update On Empty String", + name: "Should Not Config On Empty String", existingNSGroup: existingNSGroup, nsGroupID: existingNSGroup.ID, operations: []NameServerGroupUpdateOperation{ @@ -812,7 +812,7 @@ func TestUpdateNameServerGroup(t *testing.T) { errFunc: require.Error, }, { - name: "Should Not Update On Invalid Name Large String", + name: "Should Not Config On Invalid Name Large String", existingNSGroup: existingNSGroup, nsGroupID: existingNSGroup.ID, operations: []NameServerGroupUpdateOperation{ @@ -824,7 +824,7 @@ func TestUpdateNameServerGroup(t *testing.T) { errFunc: require.Error, }, { - name: "Should Not Update On Invalid On Existing Name", + name: "Should Not Config On Invalid On Existing Name", existingNSGroup: existingNSGroup, nsGroupID: existingNSGroup.ID, operations: []NameServerGroupUpdateOperation{ @@ -836,7 +836,7 @@ func TestUpdateNameServerGroup(t *testing.T) { errFunc: require.Error, }, { - name: "Should Not Update On Invalid On Multiple Name Values", + name: "Should Not Config On Invalid On Multiple Name Values", existingNSGroup: existingNSGroup, nsGroupID: existingNSGroup.ID, operations: []NameServerGroupUpdateOperation{ @@ -848,7 +848,7 @@ func TestUpdateNameServerGroup(t *testing.T) { errFunc: require.Error, }, { - name: "Should Not Update On Invalid Boolean", + name: "Should Not Config On Invalid Boolean", existingNSGroup: existingNSGroup, nsGroupID: existingNSGroup.ID, operations: []NameServerGroupUpdateOperation{ @@ -860,7 +860,7 @@ func TestUpdateNameServerGroup(t *testing.T) { errFunc: require.Error, }, { - name: "Should Not Update On Invalid Nameservers Wrong Schema", + name: "Should Not Config On Invalid Nameservers Wrong Schema", existingNSGroup: existingNSGroup, nsGroupID: existingNSGroup.ID, operations: []NameServerGroupUpdateOperation{ @@ -872,7 +872,7 @@ func TestUpdateNameServerGroup(t *testing.T) { errFunc: require.Error, }, { - name: "Should Not Update On Invalid Nameservers Wrong IP", + name: "Should Not Config On Invalid Nameservers Wrong IP", existingNSGroup: existingNSGroup, nsGroupID: existingNSGroup.ID, operations: []NameServerGroupUpdateOperation{ @@ -884,7 +884,7 @@ func TestUpdateNameServerGroup(t *testing.T) { errFunc: require.Error, }, { - name: "Should Not Update On Large Number Of Nameservers", + name: "Should Not Config On Large Number Of Nameservers", existingNSGroup: existingNSGroup, nsGroupID: existingNSGroup.ID, operations: []NameServerGroupUpdateOperation{ @@ -896,7 +896,7 @@ func TestUpdateNameServerGroup(t *testing.T) { errFunc: require.Error, }, { - name: "Should Not Update On Invalid GroupID", + name: "Should Not Config On Invalid GroupID", existingNSGroup: existingNSGroup, nsGroupID: existingNSGroup.ID, operations: []NameServerGroupUpdateOperation{ @@ -908,7 +908,7 @@ func TestUpdateNameServerGroup(t *testing.T) { errFunc: require.Error, }, { - name: "Should Not Update On Invalid Domains", + name: "Should Not Config On Invalid Domains", existingNSGroup: existingNSGroup, nsGroupID: existingNSGroup.ID, operations: []NameServerGroupUpdateOperation{ @@ -920,7 +920,7 @@ func TestUpdateNameServerGroup(t *testing.T) { errFunc: require.Error, }, { - name: "Should Not Update On Invalid Primary Status", + name: "Should Not Config On Invalid Primary Status", existingNSGroup: existingNSGroup, nsGroupID: existingNSGroup.ID, operations: []NameServerGroupUpdateOperation{ @@ -1056,7 +1056,7 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) { if err != nil { return nil, err } - return BuildManager(store, NewPeersUpdateManager(), nil, "") + return BuildManager(store, NewPeersUpdateManager(), nil, "", "") } func createNSStore(t *testing.T) (Store, error) { diff --git a/management/server/network.go b/management/server/network.go index b9b15b563..bd5fef861 100644 --- a/management/server/network.go +++ b/management/server/network.go @@ -2,6 +2,7 @@ package server import ( "github.com/c-robinson/iplib" + nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/route" "github.com/rs/xid" "google.golang.org/grpc/codes" @@ -23,9 +24,10 @@ const ( ) type NetworkMap struct { - Peers []*Peer - Network *Network - Routes []*route.Route + Peers []*Peer + Network *Network + Routes []*route.Route + DNSConfig nbdns.Config } type Network struct { diff --git a/management/server/peer.go b/management/server/peer.go index 747a731b0..ceda49ac4 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -1,6 +1,7 @@ package server import ( + nbdns "github.com/netbirdio/netbird/dns" "net" "strings" "time" @@ -43,7 +44,11 @@ type Peer struct { // Meta is a Peer system meta data Meta PeerSystemMeta // Name is peer's name (machine name) - Name string + Name string + // DNSLabel is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's + // domain to the peer label. e.g. peer-dns-label.netbird.cloud + DNSLabel string + // Status peer's management connection status Status *PeerStatus // The user ID that registered the peer UserID string @@ -65,6 +70,7 @@ func (p *Peer) Copy() *Peer { UserID: p.UserID, SSHKey: p.SSHKey, SSHEnabled: p.SSHEnabled, + DNSLabel: p.DNSLabel, } } @@ -156,6 +162,15 @@ func (am *DefaultAccountManager) UpdatePeer(accountID string, update *Peer) (*Pe } peer.SSHEnabled = update.SSHEnabled + existingLabels := account.getPeerDNSLabels() + + newLabel, err := getPeerHostLabel(peer.Name, existingLabels) + if err != nil { + return nil, err + } + + peer.DNSLabel = newLabel + account.UpdatePeer(peer) err = am.Store.SaveAccount(account) @@ -252,10 +267,26 @@ func (am *DefaultAccountManager) GetNetworkMap(peerPubKey string) (*NetworkMap, aclPeers := am.getPeersByACL(account, peerPubKey) routesUpdate := account.GetPeersRoutes(append(aclPeers, account.Peers[peerPubKey])) + // todo extract this with the store v2 + // this should become part of the method parameters + // to prevent slow performance when called in a parent loop + var zones []nbdns.CustomZone + peersCustomZone := getPeersCustomZone(account, am.dnsDomain) + if peersCustomZone.Domain != "" { + zones = append(zones, peersCustomZone) + } + + dnsUpdate := nbdns.Config{ + ServiceEnable: true, + CustomZones: zones, + NameServerGroups: getPeerNSGroups(account, peerPubKey), + } + return &NetworkMap{ - Peers: aclPeers, - Network: account.Network.Copy(), - Routes: routesUpdate, + Peers: aclPeers, + Network: account.Network.Copy(), + Routes: routesUpdate, + DNSConfig: dnsUpdate, }, err } @@ -337,10 +368,21 @@ func (am *DefaultAccountManager) AddPeer(setupKey string, userID string, peer *P } var takenIps []net.IP - for _, peer := range account.Peers { - takenIps = append(takenIps, peer.IP) + existingLabels := make(lookupMap) + for _, existingPeer := range account.Peers { + takenIps = append(takenIps, existingPeer.IP) + if existingPeer.DNSLabel != "" { + existingLabels[existingPeer.DNSLabel] = struct{}{} + } } + newLabel, err := getPeerHostLabel(peer.Name, existingLabels) + if err != nil { + return nil, err + } + + peer.DNSLabel = newLabel + network := account.Network nextIp, err := AllocatePeerIP(network.Net, takenIps) if err != nil { @@ -353,6 +395,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey string, userID string, peer *P IP: nextIp, Meta: peer.Meta, Name: peer.Name, + DNSLabel: newLabel, UserID: userID, Status: &PeerStatus{Connected: false, LastSeen: time.Now()}, SSHEnabled: false, @@ -517,11 +560,21 @@ func (am *DefaultAccountManager) updateAccountPeers(account *Account) error { // notify other peers of the change peers := account.GetPeers() network := account.Network.Copy() + var zones []nbdns.CustomZone + peersCustomZone := getPeersCustomZone(account, am.dnsDomain) + if peersCustomZone.Domain != "" { + zones = append(zones, peersCustomZone) + } for _, peer := range peers { aclPeers := am.getPeersByACL(account, peer.Key) peersUpdate := toRemotePeerConfig(aclPeers) routesUpdate := toProtocolRoutes(account.GetPeersRoutes(append(aclPeers, peer))) + dnsUpdate := toProtocolDNSConfig(nbdns.Config{ + ServiceEnable: true, + CustomZones: zones, + NameServerGroups: getPeerNSGroups(account, peer.Key), + }) err := am.peersUpdateManager.SendUpdate(peer.Key, &UpdateMessage{ Update: &proto.SyncResponse{ @@ -535,6 +588,7 @@ func (am *DefaultAccountManager) updateAccountPeers(account *Account) error { RemotePeersIsEmpty: len(peersUpdate) == 0, PeerConfig: toPeerConfig(peer, network), Routes: routesUpdate, + DNSConfig: dnsUpdate, }, }, }) diff --git a/management/server/route_test.go b/management/server/route_test.go index d2a363a37..63c268fd8 100644 --- a/management/server/route_test.go +++ b/management/server/route_test.go @@ -783,7 +783,7 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) { if err != nil { return nil, err } - return BuildManager(store, NewPeersUpdateManager(), nil, "") + return BuildManager(store, NewPeersUpdateManager(), nil, "", "") } func createRouterStore(t *testing.T) (Store, error) {