mirror of
https://github.com/netbirdio/netbird.git
synced 2025-01-07 14:39:10 +01:00
8b0398c0db
* Feat add basic support for IPv6 networks Newly generated networks automatically generate an IPv6 prefix of size 64 within the ULA address range, devices obtain a randomly generated address within this prefix. Currently, this is Linux only and does not yet support all features (routes currently cause an error). * Fix firewall configuration for IPv6 networks * Fix routing configuration for IPv6 networks * Feat provide info on IPv6 support for specific client to mgmt server * Feat allow configuration of IPv6 support through API, improve stability * Feat add IPv6 support to new firewall implementation * Fix peer list item response not containing IPv6 address * Fix nftables breaking on IPv6 address change * Fix build issues for non-linux systems * Fix intermittent disconnections when IPv6 is enabled * Fix test issues and make some minor revisions * Fix some more testing issues * Fix more CI issues due to IPv6 * Fix more testing issues * Add inheritance of IPv6 enablement status from groups * Fix IPv6 events not having associated messages * Address first review comments regarding IPv6 support * Fix IPv6 table being created even when IPv6 is disabled Also improved stability of IPv6 route and firewall handling on client side * Fix IPv6 routes not being removed * Fix DNS IPv6 issues, limit IPv6 nameservers to IPv6 peers * Improve code for IPv6 DNS server selection, add AAAA custom records * Ensure IPv6 routes can only exist for IPv6 routing peers * Fix IPv6 network generation randomness * Fix a bunch of compilation issues and test failures * Replace method calls that are unavailable in Go 1.21 * Fix nil dereference in cleanUpDefaultForwardRules6 * Fix nil pointer dereference when persisting IPv6 network in sqlite * Clean up of client-side code changes for IPv6 * Fix nil dereference in rule mangling and compilation issues * Add a bunch of client-side test cases for IPv6 * Fix IPv6 tests running on unsupported environments * Fix import cycle in tests * Add missing method SupportsIPv6() for windows * Require IPv6 default route for IPv6 tests * Fix panics in routemanager tests on non-linux * Fix some more route manager tests concerning IPv6 * Add some final client-side tests * Add IPv6 tests for management code, small fixes * Fix linting issues * Fix small test suite issues * Fix linter issues and builds on macOS and Windows again * fix builds for iOS because of IPv6 breakage
278 lines
8.0 KiB
Go
278 lines
8.0 KiB
Go
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/miekg/dns"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
nbdns "github.com/netbirdio/netbird/dns"
|
|
"github.com/netbirdio/netbird/management/proto"
|
|
"github.com/netbirdio/netbird/management/server/activity"
|
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
|
"github.com/netbirdio/netbird/management/server/status"
|
|
)
|
|
|
|
const defaultTTL = 300
|
|
|
|
type lookupMap map[string]struct{}
|
|
|
|
// DNSSettings defines dns settings at the account level
|
|
type DNSSettings struct {
|
|
// DisabledManagementGroups groups whose DNS management is disabled
|
|
DisabledManagementGroups []string `gorm:"serializer:json"`
|
|
}
|
|
|
|
// Copy returns a copy of the DNS settings
|
|
func (d DNSSettings) Copy() DNSSettings {
|
|
settings := DNSSettings{
|
|
DisabledManagementGroups: make([]string, len(d.DisabledManagementGroups)),
|
|
}
|
|
copy(settings.DisabledManagementGroups, d.DisabledManagementGroups)
|
|
return settings
|
|
}
|
|
|
|
// GetDNSSettings validates a user role and returns the DNS settings for the provided account ID
|
|
func (am *DefaultAccountManager) GetDNSSettings(accountID string, userID string) (*DNSSettings, error) {
|
|
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
|
defer unlock()
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
user, err := account.FindUser(userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !(user.HasAdminPower() || user.IsServiceUser) {
|
|
return nil, status.Errorf(status.PermissionDenied, "only users with admin power are allowed to view DNS settings")
|
|
}
|
|
dnsSettings := account.DNSSettings.Copy()
|
|
return &dnsSettings, nil
|
|
}
|
|
|
|
// SaveDNSSettings validates a user role and updates the account's DNS settings
|
|
func (am *DefaultAccountManager) SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error {
|
|
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
|
defer unlock()
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
user, err := account.FindUser(userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !user.HasAdminPower() {
|
|
return status.Errorf(status.PermissionDenied, "only users with admin power are allowed to update DNS settings")
|
|
}
|
|
|
|
if dnsSettingsToSave == nil {
|
|
return status.Errorf(status.InvalidArgument, "the dns settings provided are nil")
|
|
}
|
|
|
|
if len(dnsSettingsToSave.DisabledManagementGroups) != 0 {
|
|
err = validateGroups(dnsSettingsToSave.DisabledManagementGroups, account.Groups)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
oldSettings := account.DNSSettings.Copy()
|
|
account.DNSSettings = dnsSettingsToSave.Copy()
|
|
|
|
account.Network.IncSerial()
|
|
if err = am.Store.SaveAccount(account); err != nil {
|
|
return err
|
|
}
|
|
|
|
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}
|
|
am.StoreEvent(userID, accountID, accountID, activity.GroupAddedToDisabledManagementGroups, meta)
|
|
}
|
|
|
|
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}
|
|
am.StoreEvent(userID, accountID, accountID, activity.GroupRemovedFromDisabledManagementGroups, meta)
|
|
}
|
|
|
|
am.updateAccountPeers(account)
|
|
|
|
return nil
|
|
}
|
|
|
|
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,
|
|
SearchDomainsEnabled: nsGroup.SearchDomainsEnabled,
|
|
}
|
|
for _, ns := range nsGroup.NameServers {
|
|
protoNS := &proto.NameServer{
|
|
IP: ns.IP.String(),
|
|
Port: int64(ns.Port),
|
|
NSType: int64(ns.NSType),
|
|
}
|
|
protoGroup.NameServers = append(protoGroup.NameServers, protoNS)
|
|
}
|
|
protoUpdate.NameServerGroups = append(protoUpdate.NameServerGroups, protoGroup)
|
|
}
|
|
|
|
return protoUpdate
|
|
}
|
|
|
|
func getPeersCustomZone(account *Account, dnsDomain string, enableIPv6 bool) 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(),
|
|
})
|
|
|
|
if peer.IP6 != nil && enableIPv6 {
|
|
customZone.Records = append(customZone.Records, nbdns.SimpleRecord{
|
|
Name: dns.Fqdn(peer.DNSLabel + "." + dnsDomain),
|
|
Type: int(dns.TypeAAAA),
|
|
Class: nbdns.DefaultClass,
|
|
TTL: defaultTTL,
|
|
RData: peer.IP6.String(),
|
|
})
|
|
}
|
|
}
|
|
|
|
return customZone
|
|
}
|
|
|
|
func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup {
|
|
groupList := account.getPeerGroups(peerID)
|
|
peer := account.GetPeer(peerID)
|
|
|
|
var peerNSGroups []*nbdns.NameServerGroup
|
|
|
|
for _, nsGroup := range account.NameServerGroups {
|
|
if !nsGroup.Enabled {
|
|
continue
|
|
}
|
|
for _, gID := range nsGroup.Groups {
|
|
_, found := groupList[gID]
|
|
if found {
|
|
if !peerIsNameserver(peer, nsGroup) {
|
|
filteredNsGroup := nsGroup.Copy()
|
|
var newNameserverList []nbdns.NameServer
|
|
for _, nameserver := range filteredNsGroup.NameServers {
|
|
if nameserver.IP.Is4() || peer.IP6 != nil {
|
|
newNameserverList = append(newNameserverList, nameserver)
|
|
}
|
|
}
|
|
if len(newNameserverList) > 0 {
|
|
filteredNsGroup.NameServers = newNameserverList
|
|
peerNSGroups = append(peerNSGroups, filteredNsGroup)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return peerNSGroups
|
|
}
|
|
|
|
// peerIsNameserver returns true if the peer is a nameserver for a nsGroup
|
|
func peerIsNameserver(peer *nbpeer.Peer, nsGroup *nbdns.NameServerGroup) bool {
|
|
for _, ns := range nsGroup.NameServers {
|
|
if peer.IP.Equal(ns.IP.AsSlice()) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
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. Skipping", 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 label 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 ""
|
|
}
|