2022-11-07 15:38:21 +01:00
package server
import (
2024-07-03 11:33:02 +02:00
"context"
2022-11-07 15:38:21 +01:00
"fmt"
2023-06-28 17:29:02 +02:00
"strconv"
2024-08-07 10:52:31 +02:00
"sync"
2023-06-28 17:29:02 +02:00
log "github.com/sirupsen/logrus"
2022-11-07 15:38:21 +01:00
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/proto"
2023-01-17 17:34:40 +01:00
"github.com/netbirdio/netbird/management/server/activity"
2023-11-28 13:45:26 +01:00
nbpeer "github.com/netbirdio/netbird/management/server/peer"
2023-01-17 17:34:40 +01:00
"github.com/netbirdio/netbird/management/server/status"
2022-11-07 15:38:21 +01:00
)
2023-01-17 17:34:40 +01:00
const defaultTTL = 300
2024-08-07 10:52:31 +02:00
// DNSConfigCache is a thread-safe cache for DNS configuration components
type DNSConfigCache struct {
CustomZones sync . Map
NameServerGroups sync . Map
}
// GetCustomZone retrieves a cached custom zone
func ( c * DNSConfigCache ) GetCustomZone ( key string ) ( * proto . CustomZone , bool ) {
if c == nil {
return nil , false
}
if value , ok := c . CustomZones . Load ( key ) ; ok {
return value . ( * proto . CustomZone ) , true
}
return nil , false
}
// SetCustomZone stores a custom zone in the cache
func ( c * DNSConfigCache ) SetCustomZone ( key string , value * proto . CustomZone ) {
if c == nil {
return
}
c . CustomZones . Store ( key , value )
}
// GetNameServerGroup retrieves a cached name server group
func ( c * DNSConfigCache ) GetNameServerGroup ( key string ) ( * proto . NameServerGroup , bool ) {
if c == nil {
return nil , false
}
if value , ok := c . NameServerGroups . Load ( key ) ; ok {
return value . ( * proto . NameServerGroup ) , true
}
return nil , false
}
// SetNameServerGroup stores a name server group in the cache
func ( c * DNSConfigCache ) SetNameServerGroup ( key string , value * proto . NameServerGroup ) {
if c == nil {
return
}
c . NameServerGroups . Store ( key , value )
}
2022-11-07 15:38:21 +01:00
type lookupMap map [ string ] struct { }
2023-01-17 17:34:40 +01:00
// DNSSettings defines dns settings at the account level
type DNSSettings struct {
// DisabledManagementGroups groups whose DNS management is disabled
2023-10-12 15:42:36 +02:00
DisabledManagementGroups [ ] string ` gorm:"serializer:json" `
2023-01-17 17:34:40 +01:00
}
// Copy returns a copy of the DNS settings
2023-10-11 23:00:56 +02:00
func ( d DNSSettings ) Copy ( ) DNSSettings {
settings := DNSSettings {
DisabledManagementGroups : make ( [ ] string , len ( d . DisabledManagementGroups ) ) ,
2023-01-17 17:34:40 +01:00
}
2023-10-11 23:00:56 +02:00
copy ( settings . DisabledManagementGroups , d . DisabledManagementGroups )
2023-01-17 17:34:40 +01:00
return settings
}
// GetDNSSettings validates a user role and returns the DNS settings for the provided account ID
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) GetDNSSettings ( ctx context . Context , accountID string , userID string ) ( * DNSSettings , error ) {
2024-09-27 16:10:50 +02:00
user , err := am . Store . GetUserByUserID ( ctx , LockingStrengthShare , userID )
2023-01-17 17:34:40 +01:00
if err != nil {
return nil , err
}
2024-09-27 16:10:50 +02:00
if ! user . IsAdminOrServiceUser ( ) || user . AccountID != accountID {
2023-12-01 17:24:57 +01:00
return nil , status . Errorf ( status . PermissionDenied , "only users with admin power are allowed to view DNS settings" )
2023-01-17 17:34:40 +01:00
}
2024-09-27 16:10:50 +02:00
return am . Store . GetAccountDNSSettings ( ctx , LockingStrengthShare , accountID )
2023-01-17 17:34:40 +01:00
}
// SaveDNSSettings validates a user role and updates the account's DNS settings
2024-07-03 11:33:02 +02:00
func ( am * DefaultAccountManager ) SaveDNSSettings ( ctx context . Context , accountID string , userID string , dnsSettingsToSave * DNSSettings ) error {
2024-07-31 14:53:32 +02:00
unlock := am . Store . AcquireWriteLockByUID ( ctx , accountID )
2023-01-17 17:34:40 +01:00
defer unlock ( )
2024-07-03 11:33:02 +02:00
account , err := am . Store . GetAccount ( ctx , accountID )
2023-01-17 17:34:40 +01:00
if err != nil {
return err
}
user , err := account . FindUser ( userID )
if err != nil {
return err
}
2023-12-01 17:24:57 +01:00
if ! user . HasAdminPower ( ) {
return status . Errorf ( status . PermissionDenied , "only users with admin power are allowed to update DNS settings" )
2023-01-17 17:34:40 +01:00
}
if dnsSettingsToSave == nil {
return status . Errorf ( status . InvalidArgument , "the dns settings provided are nil" )
}
2023-01-18 14:01:50 +01:00
if len ( dnsSettingsToSave . DisabledManagementGroups ) != 0 {
err = validateGroups ( dnsSettingsToSave . DisabledManagementGroups , account . Groups )
if err != nil {
return err
}
2023-01-17 17:34:40 +01:00
}
2023-10-11 23:00:56 +02:00
oldSettings := account . DNSSettings . Copy ( )
2023-01-17 17:34:40 +01:00
account . DNSSettings = dnsSettingsToSave . Copy ( )
account . Network . IncSerial ( )
2024-07-03 11:33:02 +02:00
if err = am . Store . SaveAccount ( ctx , account ) ; err != nil {
2023-01-17 17:34:40 +01:00
return err
}
2023-01-24 10:17:24 +01:00
addedGroups := difference ( dnsSettingsToSave . DisabledManagementGroups , oldSettings . DisabledManagementGroups )
for _ , id := range addedGroups {
group := account . GetGroup ( id )
meta := map [ string ] any { "group" : group . Name , "group_id" : group . ID }
2024-07-03 11:33:02 +02:00
am . StoreEvent ( ctx , userID , accountID , accountID , activity . GroupAddedToDisabledManagementGroups , meta )
2023-01-24 10:17:24 +01:00
}
2023-01-17 17:34:40 +01:00
2023-01-24 10:17:24 +01:00
removedGroups := difference ( oldSettings . DisabledManagementGroups , dnsSettingsToSave . DisabledManagementGroups )
for _ , id := range removedGroups {
group := account . GetGroup ( id )
meta := map [ string ] any { "group" : group . Name , "group_id" : group . ID }
2024-07-03 11:33:02 +02:00
am . StoreEvent ( ctx , userID , accountID , accountID , activity . GroupRemovedFromDisabledManagementGroups , meta )
2023-01-24 10:17:24 +01:00
}
2023-01-17 17:34:40 +01:00
2024-07-03 11:33:02 +02:00
am . updateAccountPeers ( ctx , account )
2023-10-04 15:08:50 +02:00
return nil
2023-01-17 17:34:40 +01:00
}
2022-11-07 15:38:21 +01:00
2024-08-07 10:52:31 +02:00
// toProtocolDNSConfig converts nbdns.Config to proto.DNSConfig using the cache
func toProtocolDNSConfig ( update nbdns . Config , cache * DNSConfigCache ) * proto . DNSConfig {
protoUpdate := & proto . DNSConfig {
ServiceEnable : update . ServiceEnable ,
CustomZones : make ( [ ] * proto . CustomZone , 0 , len ( update . CustomZones ) ) ,
NameServerGroups : make ( [ ] * proto . NameServerGroup , 0 , len ( update . NameServerGroups ) ) ,
}
2022-11-07 15:38:21 +01:00
for _ , zone := range update . CustomZones {
2024-08-07 10:52:31 +02:00
cacheKey := zone . Domain
if cachedZone , exists := cache . GetCustomZone ( cacheKey ) ; exists {
protoUpdate . CustomZones = append ( protoUpdate . CustomZones , cachedZone )
} else {
protoZone := convertToProtoCustomZone ( zone )
cache . SetCustomZone ( cacheKey , protoZone )
protoUpdate . CustomZones = append ( protoUpdate . CustomZones , protoZone )
2022-11-07 15:38:21 +01:00
}
}
for _ , nsGroup := range update . NameServerGroups {
2024-08-07 10:52:31 +02:00
cacheKey := nsGroup . ID
if cachedGroup , exists := cache . GetNameServerGroup ( cacheKey ) ; exists {
protoUpdate . NameServerGroups = append ( protoUpdate . NameServerGroups , cachedGroup )
} else {
protoGroup := convertToProtoNameServerGroup ( nsGroup )
cache . SetNameServerGroup ( cacheKey , protoGroup )
protoUpdate . NameServerGroups = append ( protoUpdate . NameServerGroups , protoGroup )
2022-11-07 15:38:21 +01:00
}
}
return protoUpdate
}
2024-08-07 10:52:31 +02:00
// Helper function to convert nbdns.CustomZone to proto.CustomZone
func convertToProtoCustomZone ( zone nbdns . CustomZone ) * proto . CustomZone {
protoZone := & proto . CustomZone {
Domain : zone . Domain ,
Records : make ( [ ] * proto . SimpleRecord , 0 , len ( zone . Records ) ) ,
}
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 ,
} )
2022-11-07 15:38:21 +01:00
}
2024-08-07 10:52:31 +02:00
return protoZone
}
2022-11-07 15:38:21 +01:00
2024-08-07 10:52:31 +02:00
// Helper function to convert nbdns.NameServerGroup to proto.NameServerGroup
func convertToProtoNameServerGroup ( nsGroup * nbdns . NameServerGroup ) * proto . NameServerGroup {
protoGroup := & proto . NameServerGroup {
Primary : nsGroup . Primary ,
Domains : nsGroup . Domains ,
SearchDomainsEnabled : nsGroup . SearchDomainsEnabled ,
NameServers : make ( [ ] * proto . NameServer , 0 , len ( nsGroup . NameServers ) ) ,
}
for _ , ns := range nsGroup . NameServers {
protoGroup . NameServers = append ( protoGroup . NameServers , & proto . NameServer {
IP : ns . IP . String ( ) ,
Port : int64 ( ns . Port ) ,
NSType : int64 ( ns . NSType ) ,
2022-11-07 15:38:21 +01:00
} )
}
2024-08-07 10:52:31 +02:00
return protoGroup
2022-11-07 15:38:21 +01:00
}
func getPeerNSGroups ( account * Account , peerID string ) [ ] * nbdns . NameServerGroup {
2022-12-06 10:11:57 +01:00
groupList := account . getPeerGroups ( peerID )
2022-11-07 15:38:21 +01:00
var peerNSGroups [ ] * nbdns . NameServerGroup
for _ , nsGroup := range account . NameServerGroups {
if ! nsGroup . Enabled {
continue
}
for _ , gID := range nsGroup . Groups {
_ , found := groupList [ gID ]
if found {
2023-06-28 17:29:02 +02:00
if ! peerIsNameserver ( account . GetPeer ( peerID ) , nsGroup ) {
peerNSGroups = append ( peerNSGroups , nsGroup . Copy ( ) )
break
}
2022-11-07 15:38:21 +01:00
}
}
}
return peerNSGroups
}
2023-06-28 17:29:02 +02:00
// peerIsNameserver returns true if the peer is a nameserver for a nsGroup
2023-11-28 13:45:26 +01:00
func peerIsNameserver ( peer * nbpeer . Peer , nsGroup * nbdns . NameServerGroup ) bool {
2023-06-28 17:29:02 +02:00
for _ , ns := range nsGroup . NameServers {
if peer . IP . Equal ( ns . IP . AsSlice ( ) ) {
return true
}
}
return false
}
2024-07-03 11:33:02 +02:00
func addPeerLabelsToAccount ( ctx context . Context , account * Account , peerLabels lookupMap ) {
2022-11-07 15:38:21 +01:00
for _ , peer := range account . Peers {
label , err := getPeerHostLabel ( peer . Name , peerLabels )
if err != nil {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . 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 )
2022-11-07 15:38:21 +01:00
label , err = getPeerHostLabel ( peer . Meta . Hostname , peerLabels )
if err != nil {
2024-07-03 11:33:02 +02:00
log . WithContext ( ctx ) . Errorf ( "got another error while generating a peer host label with hostname. Peer hostname %s, error: %v. Skipping" , peer . Meta . Hostname , err )
2022-11-07 15:38:21 +01:00
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 == "" {
2022-11-08 16:14:36 +01:00
return "" , fmt . Errorf ( "couldn't find a unique valid label for %s, parsed label %s" , name , label )
2022-11-07 15:38:21 +01:00
}
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 ""
}