Explorar el Código

resolve merge conflicts

abhishek9686 hace 2 semanas
padre
commit
e25aa63916
Se han modificado 12 ficheros con 516 adiciones y 6 borrados
  1. 256 0
      controllers/dns.go
  2. 1 0
      controllers/hosts.go
  3. 173 0
      logic/dns.go
  4. 1 0
      logic/peers.go
  5. 10 0
      models/dnsEntry.go
  6. 1 0
      models/events.go
  7. 5 0
      models/mqtt.go
  8. 2 0
      models/structs.go
  9. 1 0
      mq/publishers.go
  10. 15 6
      pro/controllers/users.go
  11. 50 0
      schema/dns.go
  12. 1 0
      schema/models.go

+ 256 - 0
controllers/dns.go

@@ -1,19 +1,25 @@
 package controller
 
 import (
+	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
 	"net/http"
 	"strings"
+	"time"
 
+	"github.com/google/uuid"
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/db"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/mq"
+	"github.com/gravitl/netmaker/schema"
 	"github.com/gravitl/netmaker/servercfg"
+	"gorm.io/datatypes"
 )
 
 func dnsHandlers(r *mux.Router) {
@@ -34,6 +40,256 @@ func dnsHandlers(r *mux.Router) {
 		Methods(http.MethodPost)
 	r.HandleFunc("/api/dns/{network}/{domain}", logic.SecurityCheck(true, http.HandlerFunc(deleteDNS))).
 		Methods(http.MethodDelete)
+	r.HandleFunc("/api/v1/nameserver", logic.SecurityCheck(true, http.HandlerFunc(createNs))).Methods(http.MethodPost)
+	r.HandleFunc("/api/v1/nameserver", logic.SecurityCheck(true, http.HandlerFunc(listNs))).Methods(http.MethodGet)
+	r.HandleFunc("/api/v1/nameserver", logic.SecurityCheck(true, http.HandlerFunc(updateNs))).Methods(http.MethodPut)
+	r.HandleFunc("/api/v1/nameserver", logic.SecurityCheck(true, http.HandlerFunc(deleteNs))).Methods(http.MethodDelete)
+	r.HandleFunc("/api/v1/nameserver/global", logic.SecurityCheck(true, http.HandlerFunc(getGlobalNs))).Methods(http.MethodGet)
+}
+
+// @Summary     List Global Nameservers
+// @Router      /api/v1/nameserver/global [get]
+// @Tags        Auth
+// @Accept      json
+// @Param       query network string
+// @Success     200 {object} models.SuccessResponse
+// @Failure     400 {object} models.ErrorResponse
+// @Failure     401 {object} models.ErrorResponse
+// @Failure     500 {object} models.ErrorResponse
+func getGlobalNs(w http.ResponseWriter, r *http.Request) {
+
+	logic.ReturnSuccessResponseWithJson(w, r, logic.GlobalNsList, "fetched nameservers")
+}
+
+// @Summary     Create Nameserver
+// @Router      /api/v1/nameserver [post]
+// @Tags        DNS
+// @Accept      json
+// @Param       body body models.NameserverReq
+// @Success     200 {object} models.SuccessResponse
+// @Failure     400 {object} models.ErrorResponse
+// @Failure     401 {object} models.ErrorResponse
+// @Failure     500 {object} models.ErrorResponse
+func createNs(w http.ResponseWriter, r *http.Request) {
+
+	var req schema.Nameserver
+	err := json.NewDecoder(r.Body).Decode(&req)
+	if err != nil {
+		logger.Log(0, "error decoding request body: ",
+			err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	if err := logic.ValidateNameserverReq(req); err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	if req.Tags == nil {
+		req.Tags = make(datatypes.JSONMap)
+	}
+	if gNs, ok := logic.GlobalNsList[req.Name]; ok {
+		req.Servers = gNs.IPs
+	}
+	ns := schema.Nameserver{
+		ID:          uuid.New().String(),
+		Name:        req.Name,
+		NetworkID:   req.NetworkID,
+		Description: req.Description,
+		MatchDomain: req.MatchDomain,
+		Servers:     req.Servers,
+		Tags:        req.Tags,
+		Status:      true,
+		CreatedBy:   r.Header.Get("user"),
+		CreatedAt:   time.Now().UTC(),
+	}
+
+	err = ns.Create(db.WithContext(r.Context()))
+	if err != nil {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(errors.New("error creating nameserver "+err.Error()), logic.Internal),
+		)
+		return
+	}
+	logic.LogEvent(&models.Event{
+		Action: models.Create,
+		Source: models.Subject{
+			ID:   r.Header.Get("user"),
+			Name: r.Header.Get("user"),
+			Type: models.UserSub,
+		},
+		TriggeredBy: r.Header.Get("user"),
+		Target: models.Subject{
+			ID:   ns.ID,
+			Name: ns.Name,
+			Type: models.NameserverSub,
+		},
+		NetworkID: models.NetworkID(ns.NetworkID),
+		Origin:    models.Dashboard,
+	})
+
+	go mq.PublishPeerUpdate(false)
+	logic.ReturnSuccessResponseWithJson(w, r, ns, "created nameserver")
+}
+
+// @Summary     List Nameservers
+// @Router      /api/v1/nameserver [get]
+// @Tags        Auth
+// @Accept      json
+// @Param       query network string
+// @Success     200 {object} models.SuccessResponse
+// @Failure     400 {object} models.ErrorResponse
+// @Failure     401 {object} models.ErrorResponse
+// @Failure     500 {object} models.ErrorResponse
+func listNs(w http.ResponseWriter, r *http.Request) {
+
+	network := r.URL.Query().Get("network")
+	if network == "" {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("network is required"), "badrequest"))
+		return
+	}
+	ns := schema.Nameserver{NetworkID: network}
+	list, err := ns.ListByNetwork(db.WithContext(r.Context()))
+	if err != nil {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(errors.New("error listing nameservers "+err.Error()), "internal"),
+		)
+		return
+	}
+	logic.ReturnSuccessResponseWithJson(w, r, list, "fetched nameservers")
+}
+
+// @Summary     Update Nameserver
+// @Router      /api/v1/nameserver [put]
+// @Tags        Auth
+// @Accept      json
+// @Param       body body models.NameserverReq
+// @Success     200 {object} models.SuccessResponse
+// @Failure     400 {object} models.ErrorResponse
+// @Failure     401 {object} models.ErrorResponse
+// @Failure     500 {object} models.ErrorResponse
+func updateNs(w http.ResponseWriter, r *http.Request) {
+
+	var updateNs schema.Nameserver
+	err := json.NewDecoder(r.Body).Decode(&updateNs)
+	if err != nil {
+		logger.Log(0, "error decoding request body: ",
+			err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+
+	if err := logic.ValidateUpdateNameserverReq(updateNs); err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	if updateNs.Tags == nil {
+		updateNs.Tags = make(datatypes.JSONMap)
+	}
+
+	ns := schema.Nameserver{ID: updateNs.ID}
+	err = ns.Get(db.WithContext(r.Context()))
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	var updateStatus bool
+	if updateNs.Status != ns.Status {
+		updateStatus = true
+	}
+	event := &models.Event{
+		Action: models.Update,
+		Source: models.Subject{
+			ID:   r.Header.Get("user"),
+			Name: r.Header.Get("user"),
+			Type: models.UserSub,
+		},
+		TriggeredBy: r.Header.Get("user"),
+		Target: models.Subject{
+			ID:   ns.ID,
+			Name: updateNs.Name,
+			Type: models.NameserverSub,
+		},
+		Diff: models.Diff{
+			Old: ns,
+			New: updateNs,
+		},
+		NetworkID: models.NetworkID(ns.NetworkID),
+		Origin:    models.Dashboard,
+	}
+	ns.Servers = updateNs.Servers
+	ns.Tags = updateNs.Tags
+	ns.Description = updateNs.Description
+	ns.Name = updateNs.Name
+	ns.Status = updateNs.Status
+	ns.UpdatedAt = time.Now().UTC()
+
+	err = ns.Update(db.WithContext(context.TODO()))
+	if err != nil {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(errors.New("error creating egress resource"+err.Error()), "internal"),
+		)
+		return
+	}
+	if updateStatus {
+		ns.UpdateStatus(db.WithContext(context.TODO()))
+	}
+	logic.LogEvent(event)
+	go mq.PublishPeerUpdate(false)
+	logic.ReturnSuccessResponseWithJson(w, r, ns, "updated nameserver")
+}
+
+// @Summary     Delete Nameserver Resource
+// @Router      /api/v1/nameserver [delete]
+// @Tags        Auth
+// @Accept      json
+// @Param       body body models.Egress
+// @Success     200 {object} models.SuccessResponse
+// @Failure     400 {object} models.ErrorResponse
+// @Failure     401 {object} models.ErrorResponse
+// @Failure     500 {object} models.ErrorResponse
+func deleteNs(w http.ResponseWriter, r *http.Request) {
+
+	id := r.URL.Query().Get("id")
+	if id == "" {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("id is required"), "badrequest"))
+		return
+	}
+	ns := schema.Nameserver{ID: id}
+	err := ns.Get(db.WithContext(r.Context()))
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, logic.BadReq))
+		return
+	}
+	err = ns.Delete(db.WithContext(r.Context()))
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, logic.Internal))
+		return
+	}
+	logic.LogEvent(&models.Event{
+		Action: models.Delete,
+		Source: models.Subject{
+			ID:   r.Header.Get("user"),
+			Name: r.Header.Get("user"),
+			Type: models.UserSub,
+		},
+		TriggeredBy: r.Header.Get("user"),
+		Target: models.Subject{
+			ID:   ns.ID,
+			Name: ns.Name,
+			Type: models.NameserverSub,
+		},
+		NetworkID: models.NetworkID(ns.NetworkID),
+		Origin:    models.Dashboard,
+	})
+
+	go mq.PublishPeerUpdate(false)
+	logic.ReturnSuccessResponseWithJson(w, r, nil, "deleted nameserver resource")
 }
 
 // @Summary     Gets node DNS entries associated with a network

+ 1 - 0
controllers/hosts.go

@@ -256,6 +256,7 @@ func pull(w http.ResponseWriter, r *http.Request) {
 		NameServers:       hPU.NameServers,
 		EgressWithDomains: hPU.EgressWithDomains,
 		EndpointDetection: logic.IsEndpointDetectionEnabled(),
+		DnsNameservers:    hPU.DnsNameservers,
 	}
 
 	logger.Log(1, hostID, "completed a pull")

+ 173 - 0
logic/dns.go

@@ -9,6 +9,7 @@ import (
 	"os"
 	"regexp"
 	"sort"
+	"strings"
 
 	validator "github.com/go-playground/validator/v10"
 	"github.com/gravitl/netmaker/database"
@@ -19,6 +20,41 @@ import (
 	"github.com/txn2/txeh"
 )
 
+type GlobalNs struct {
+	ID  string   `json:"id"`
+	IPs []string `json:"ips"`
+}
+
+var GlobalNsList = map[string]GlobalNs{
+	"Google": {
+		ID: "Google",
+		IPs: []string{
+			"8.8.8.8",
+			"8.8.4.4",
+			"2001:4860:4860::8888",
+			"2001:4860:4860::8844",
+		},
+	},
+	"Cloudflare": {
+		ID: "Cloudflare",
+		IPs: []string{
+			"1.1.1.1",
+			"1.0.0.1",
+			"2606:4700:4700::1111",
+			"2606:4700:4700::1001",
+		},
+	},
+	"Quad9": {
+		ID: "Quad9",
+		IPs: []string{
+			"9.9.9.9",
+			"149.112.112.112",
+			"2620:fe::fe",
+			"2620:fe::9",
+		},
+	},
+}
+
 // SetDNS - sets the dns on file
 func SetDNS() error {
 	hostfile, err := txeh.NewHosts(&txeh.HostsConfig{})
@@ -354,3 +390,140 @@ func CreateDNS(entry models.DNSEntry) (models.DNSEntry, error) {
 	err = database.Insert(k, string(data), database.DNS_TABLE_NAME)
 	return entry, err
 }
+
+func ValidateNameserverReq(ns schema.Nameserver) error {
+	if ns.Name == "" {
+		return errors.New("name is required")
+	}
+	if ns.NetworkID == "" {
+		return errors.New("network is required")
+	}
+	if len(ns.Servers) == 0 {
+		return errors.New("atleast one nameserver should be specified")
+	}
+	if !IsValidMatchDomain(ns.MatchDomain) {
+		return errors.New("invalid match domain")
+	}
+	return nil
+}
+
+func ValidateUpdateNameserverReq(updateNs schema.Nameserver) error {
+	if updateNs.Name == "" {
+		return errors.New("name is required")
+	}
+	if len(updateNs.Servers) == 0 {
+		return errors.New("atleast one nameserver should be specified")
+	}
+	if !IsValidMatchDomain(updateNs.MatchDomain) {
+		return errors.New("invalid match domain")
+	}
+	return nil
+}
+
+func GetNameserversForHost(h *models.Host) (returnNsLi []models.Nameserver) {
+	if h.DNS != "yes" {
+		return
+	}
+	for _, nodeID := range h.Nodes {
+		node, err := GetNodeByID(nodeID)
+		if err != nil {
+			continue
+		}
+		ns := &schema.Nameserver{
+			NetworkID: node.Network,
+		}
+		nsLi, _ := ns.ListByNetwork(db.WithContext(context.TODO()))
+		for _, nsI := range nsLi {
+			if !nsI.Status {
+				continue
+			}
+			_, all := nsI.Tags["*"]
+			if all {
+				returnNsLi = append(returnNsLi, models.Nameserver{
+					IPs:         nsI.Servers,
+					MatchDomain: nsI.MatchDomain,
+				})
+				continue
+			}
+			for tagI := range node.Tags {
+				if _, ok := nsI.Tags[tagI.String()]; ok {
+					returnNsLi = append(returnNsLi, models.Nameserver{
+						IPs:         nsI.Servers,
+						MatchDomain: nsI.MatchDomain,
+					})
+				}
+			}
+		}
+		if node.IsInternetGateway {
+			globalNs := models.Nameserver{
+				MatchDomain: ".",
+			}
+			for _, nsI := range GlobalNsList {
+				globalNs.IPs = append(globalNs.IPs, nsI.IPs...)
+			}
+			returnNsLi = append(returnNsLi, globalNs)
+		}
+	}
+	return
+}
+
+// IsValidMatchDomain reports whether s is a valid "match domain".
+// Rules (simple/ASCII):
+//   - "~." is allowed (match all).
+//   - Optional leading "~" allowed (e.g., "~example.com").
+//   - Optional single trailing "." allowed (FQDN form).
+//   - No wildcards "*", no leading ".", no underscores.
+//   - Labels: letters/digits/hyphen (LDH), 1–63 chars, no leading/trailing hyphen.
+//   - Total length (without trailing dot) ≤ 253.
+func IsValidMatchDomain(s string) bool {
+	s = strings.TrimSpace(s)
+	if s == "" {
+		return false
+	}
+	if s == "~." { // special case: match-all
+		return true
+	}
+
+	// Strip optional leading "~"
+	if strings.HasPrefix(s, "~") {
+		s = s[1:]
+		if s == "" {
+			return false
+		}
+	}
+
+	// Allow exactly one trailing dot
+	if strings.HasSuffix(s, ".") {
+		s = s[:len(s)-1]
+		if s == "" {
+			return false
+		}
+	}
+
+	// Disallow leading dot, wildcards, underscores
+	if strings.HasPrefix(s, ".") || strings.Contains(s, "*") || strings.Contains(s, "_") {
+		return false
+	}
+
+	// Lowercase for ASCII checks
+	s = strings.ToLower(s)
+
+	// Length check
+	if len(s) > 253 {
+		return false
+	}
+
+	// Label regex: LDH, 1–63, no leading/trailing hyphen
+	reLabel := regexp.MustCompile(`^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$`)
+
+	parts := strings.Split(s, ".")
+	for _, lbl := range parts {
+		if len(lbl) == 0 || len(lbl) > 63 {
+			return false
+		}
+		if !reLabel.MatchString(lbl) {
+			return false
+		}
+	}
+	return true
+}

+ 1 - 0
logic/peers.go

@@ -142,6 +142,7 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 		NodePeers:       []wgtypes.PeerConfig{},
 		HostNetworkInfo: models.HostInfoMap{},
 		ServerConfig:    GetServerInfo(),
+		DnsNameservers:  GetNameserversForHost(host),
 	}
 	if host.DNS == "no" {
 		hostPeerUpdate.ManageDNS = false

+ 10 - 0
models/dnsEntry.go

@@ -47,3 +47,13 @@ type DNSEntry struct {
 	Name     string `json:"name" validate:"required,name_unique,min=1,max=192,whitespace"`
 	Network  string `json:"network" validate:"network_exists"`
 }
+
+type NameserverReq struct {
+	Name        string   `json:"name"`
+	Network     string   `json:"network"`
+	Description string   ` json:"description"`
+	Servers     []string `json:"servers"`
+	MatchDomain string   `json:"match_domain"`
+	Tags        []string `json:"tags"`
+	Status      bool     `gorm:"status" json:"status"`
+}

+ 1 - 0
models/events.go

@@ -53,6 +53,7 @@ const (
 	DashboardSub       SubjectType = "DASHBOARD"
 	EnrollmentKeySub   SubjectType = "ENROLLMENT_KEY"
 	ClientAppSub       SubjectType = "CLIENT-APP"
+	NameserverSub      SubjectType = "NAMESERVER"
 )
 
 func (sub SubjectType) String() string {

+ 5 - 0
models/mqtt.go

@@ -29,6 +29,7 @@ type HostPeerUpdate struct {
 	ReplacePeers      bool                  `json:"replace_peers"`
 	NameServers       []string              `json:"name_servers"`
 	EgressWithDomains []EgressDomain        `json:"egress_with_domains"`
+	DnsNameservers    []Nameserver          `json:"dns_nameservers"`
 	ServerConfig
 	OldPeerUpdateFields
 }
@@ -39,6 +40,10 @@ type EgressDomain struct {
 	Host   Host   `json:"host"`
 	Domain string `json:"domain"`
 }
+type Nameserver struct {
+	IPs         []string `json:"ips"`
+	MatchDomain string   `json:"match_domain"`
+}
 
 type OldPeerUpdateFields struct {
 	NodePeers         []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`

+ 2 - 0
models/structs.go

@@ -53,6 +53,7 @@ type UserRemoteGws struct {
 	Status            NodeStatus `json:"status"`
 	DnsAddress        string     `json:"dns_address"`
 	Addresses         string     `json:"addresses"`
+	MatchDomains      []string   `json:"match_domains"`
 }
 
 // UserRAGs - struct for user access gws
@@ -263,6 +264,7 @@ type HostPull struct {
 	EndpointDetection bool                  `json:"endpoint_detection"`
 	NameServers       []string              `json:"name_servers"`
 	EgressWithDomains []EgressDomain        `json:"egress_with_domains"`
+	DnsNameservers    []Nameserver          `json:"dns_nameservers"`
 }
 
 type DefaultGwInfo struct {

+ 1 - 0
mq/publishers.go

@@ -113,6 +113,7 @@ func PublishSingleHostPeerUpdate(host *models.Host, allNodes []models.Node, dele
 	if err != nil {
 		return err
 	}
+
 	for _, nodeID := range host.Nodes {
 
 		node, err := logic.GetNodeByID(nodeID)

+ 15 - 6
pro/controllers/users.go

@@ -5,15 +5,16 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"github.com/gravitl/netmaker/pro/idp"
-	"github.com/gravitl/netmaker/pro/idp/azure"
-	"github.com/gravitl/netmaker/pro/idp/google"
-	"github.com/gravitl/netmaker/pro/idp/okta"
 	"net/http"
 	"net/url"
 	"strings"
 	"time"
 
+	"github.com/gravitl/netmaker/pro/idp"
+	"github.com/gravitl/netmaker/pro/idp/azure"
+	"github.com/gravitl/netmaker/pro/idp/google"
+	"github.com/gravitl/netmaker/pro/idp/okta"
+
 	"github.com/google/uuid"
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/database"
@@ -1477,7 +1478,7 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 			logic.GetPeerListenPort(host),
 		)
 		gwClient.AllowedIPs = logic.GetExtclientAllowedIPs(gwClient)
-		gws = append(gws, models.UserRemoteGws{
+		gw := models.UserRemoteGws{
 			GwID:              node.ID.String(),
 			GWName:            host.Name,
 			Network:           node.Network,
@@ -1492,7 +1493,15 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 			Status:            node.Status,
 			DnsAddress:        node.IngressDNS,
 			Addresses:         utils.NoEmptyStringToCsv(node.Address.String(), node.Address6.String()),
-		})
+		}
+		if !node.IsInternetGateway {
+			hNs := logic.GetNameserversForHost(host)
+			for _, nsI := range hNs {
+				gw.MatchDomains = append(gw.MatchDomains, nsI.MatchDomain)
+			}
+		}
+
+		gws = append(gws, gw)
 		userGws[node.Network] = gws
 		delete(userGwNodes, node.ID.String())
 	}

+ 50 - 0
schema/dns.go

@@ -0,0 +1,50 @@
+package schema
+
+import (
+	"context"
+	"time"
+
+	"github.com/gravitl/netmaker/db"
+	"gorm.io/datatypes"
+)
+
+type Nameserver struct {
+	ID          string                      `gorm:"primaryKey" json:"id"`
+	Name        string                      `gorm:"name" json:"name"`
+	NetworkID   string                      `gorm:"network_id" json:"network_id"`
+	Description string                      `gorm:"description" json:"description"`
+	Servers     datatypes.JSONSlice[string] `gorm:"servers" json:"servers"`
+	MatchDomain string                      `gorm:"match_domain" json:"match_domain"`
+	Tags        datatypes.JSONMap           `gorm:"tags" json:"tags"`
+	Status      bool                        `gorm:"status" json:"status"`
+	CreatedBy   string                      `gorm:"created_by" json:"created_by"`
+	CreatedAt   time.Time                   `gorm:"created_at" json:"created_at"`
+	UpdatedAt   time.Time                   `gorm:"updated_at" json:"updated_at"`
+}
+
+func (ns *Nameserver) Get(ctx context.Context) error {
+	return db.FromContext(ctx).Model(&Nameserver{}).First(&ns).Where("id = ?", ns.ID).Error
+}
+
+func (ns *Nameserver) Update(ctx context.Context) error {
+	return db.FromContext(ctx).Model(&Nameserver{}).Where("id = ?", ns.ID).Updates(&ns).Error
+}
+
+func (ns *Nameserver) Create(ctx context.Context) error {
+	return db.FromContext(ctx).Model(&Nameserver{}).Create(&ns).Error
+}
+
+func (ns *Nameserver) ListByNetwork(ctx context.Context) (dnsli []Nameserver, err error) {
+	err = db.FromContext(ctx).Model(&Nameserver{}).Where("network_id = ?", ns.NetworkID).Find(&dnsli).Error
+	return
+}
+
+func (ns *Nameserver) Delete(ctx context.Context) error {
+	return db.FromContext(ctx).Model(&Nameserver{}).Where("id = ?", ns.ID).Delete(&ns).Error
+}
+
+func (ns *Nameserver) UpdateStatus(ctx context.Context) error {
+	return db.FromContext(ctx).Model(&Nameserver{}).Where("id = ?", ns.ID).Updates(map[string]any{
+		"status": ns.Status,
+	}).Error
+}

+ 1 - 0
schema/models.go

@@ -8,5 +8,6 @@ func ListModels() []interface{} {
 		&UserAccessToken{},
 		&Event{},
 		&PendingHost{},
+		&Nameserver{},
 	}
 }