fix cluster logic for domains and reverse proxy

This commit is contained in:
mlsmaycon
2026-02-07 11:43:01 +01:00
parent f65f4fc280
commit 2f263bf7e6
6 changed files with 81 additions and 41 deletions
@@ -5,7 +5,6 @@ import (
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
nbcontext "github.com/netbirdio/netbird/management/server/context" nbcontext "github.com/netbirdio/netbird/management/server/context"
"github.com/netbirdio/netbird/shared/management/http/api" "github.com/netbirdio/netbird/shared/management/http/api"
"github.com/netbirdio/netbird/shared/management/http/util" "github.com/netbirdio/netbird/shared/management/http/util"
@@ -40,12 +39,16 @@ func domainTypeToApi(t domainType) api.ReverseProxyDomainType {
} }
func domainToApi(d *Domain) api.ReverseProxyDomain { func domainToApi(d *Domain) api.ReverseProxyDomain {
return api.ReverseProxyDomain{ resp := api.ReverseProxyDomain{
Domain: d.Domain, Domain: d.Domain,
Id: d.ID, Id: d.ID,
Type: domainTypeToApi(d.Type), Type: domainTypeToApi(d.Type),
Validated: d.Validated, Validated: d.Validated,
} }
if d.TargetCluster != "" {
resp.TargetCluster = &d.TargetCluster
}
return resp
} }
func (h *handler) getAllDomains(w http.ResponseWriter, r *http.Request) { func (h *handler) getAllDomains(w http.ResponseWriter, r *http.Request) {
@@ -82,7 +85,7 @@ func (h *handler) createCustomDomain(w http.ResponseWriter, r *http.Request) {
return return
} }
domain, err := h.manager.CreateDomain(r.Context(), userAuth.AccountId, req.Domain) domain, err := h.manager.CreateDomain(r.Context(), userAuth.AccountId, req.Domain, req.TargetCluster)
if err != nil { if err != nil {
util.WriteError(r.Context(), err, w) util.WriteError(r.Context(), err, w)
return return
@@ -19,11 +19,12 @@ const (
) )
type Domain struct { type Domain struct {
ID string `gorm:"unique;primaryKey;autoIncrement"` ID string `gorm:"unique;primaryKey;autoIncrement"`
Domain string `gorm:"unique"` // Domain records must be unique, this avoids domain reuse across accounts. Domain string `gorm:"unique"` // Domain records must be unique, this avoids domain reuse across accounts.
AccountID string `gorm:"index"` AccountID string `gorm:"index"`
Type domainType `gorm:"-"` TargetCluster string // The proxy cluster this domain should be validated against
Validated bool Type domainType `gorm:"-"`
Validated bool
} }
type store interface { type store interface {
@@ -32,7 +33,7 @@ type store interface {
GetCustomDomain(ctx context.Context, accountID string, domainID string) (*Domain, error) GetCustomDomain(ctx context.Context, accountID string, domainID string) (*Domain, error)
ListFreeDomains(ctx context.Context, accountID string) ([]string, error) ListFreeDomains(ctx context.Context, accountID string) ([]string, error)
ListCustomDomains(ctx context.Context, accountID string) ([]*Domain, error) ListCustomDomains(ctx context.Context, accountID string) ([]*Domain, error)
CreateCustomDomain(ctx context.Context, accountID string, domainName string, validated bool) (*Domain, error) CreateCustomDomain(ctx context.Context, accountID string, domainName string, targetCluster string, validated bool) (*Domain, error)
UpdateCustomDomain(ctx context.Context, accountID string, d *Domain) (*Domain, error) UpdateCustomDomain(ctx context.Context, accountID string, d *Domain) (*Domain, error)
DeleteCustomDomain(ctx context.Context, accountID string, domainID string) error DeleteCustomDomain(ctx context.Context, accountID string, domainID string) error
} }
@@ -85,31 +86,43 @@ func (m Manager) GetDomains(ctx context.Context, accountID string) ([]*Domain, e
// Add custom domains. // Add custom domains.
for _, domain := range domains { for _, domain := range domains {
ret = append(ret, &Domain{ ret = append(ret, &Domain{
ID: domain.ID, ID: domain.ID,
Domain: domain.Domain, Domain: domain.Domain,
AccountID: accountID, AccountID: accountID,
Type: TypeCustom, TargetCluster: domain.TargetCluster,
Validated: domain.Validated, Type: TypeCustom,
Validated: domain.Validated,
}) })
} }
return ret, nil return ret, nil
} }
func (m Manager) CreateDomain(ctx context.Context, accountID, domainName string) (*Domain, error) { func (m Manager) CreateDomain(ctx context.Context, accountID, domainName, targetCluster string) (*Domain, error) {
// Attempt an initial validation; however, a failure is still acceptable for creation
// because the user may not yet have configured their DNS records, or the DNS update // Verify the target cluster is in the available clusters
// has not yet reached the servers that are queried by the validation resolver. allowList := m.proxyURLAllowList()
clusterValid := false
for _, cluster := range allowList {
if cluster == targetCluster {
clusterValid = true
break
}
}
if !clusterValid {
return nil, fmt.Errorf("target cluster %s is not available", targetCluster)
}
// Attempt an initial validation against the specified cluster only
var validated bool var validated bool
if m.validator.IsValid(ctx, domainName, m.proxyURLAllowList()) { if m.validator.IsValid(ctx, domainName, []string{targetCluster}) {
validated = true validated = true
} }
d, err := m.store.CreateCustomDomain(ctx, accountID, domainName, validated) d, err := m.store.CreateCustomDomain(ctx, accountID, domainName, targetCluster, validated)
if err != nil { if err != nil {
return d, fmt.Errorf("create domain in store: %w", err) return d, fmt.Errorf("create domain in store: %w", err)
} }
return d, nil return d, nil
} }
@@ -136,15 +149,25 @@ func (m Manager) ValidateDomain(accountID, domainID string) {
return return
} }
allowList := m.proxyURLAllowList() // Validate only against the domain's target cluster
log.WithFields(log.Fields{ targetCluster := d.TargetCluster
"accountID": accountID, if targetCluster == "" {
"domainID": domainID, log.WithFields(log.Fields{
"domain": d.Domain, "accountID": accountID,
"proxyAllowList": allowList, "domainID": domainID,
}).Info("validating domain against proxy allow list") "domain": d.Domain,
}).Warn("domain has no target cluster set, skipping validation")
return
}
if m.validator.IsValid(context.Background(), d.Domain, allowList) { log.WithFields(log.Fields{
"accountID": accountID,
"domainID": domainID,
"domain": d.Domain,
"targetCluster": targetCluster,
}).Info("validating domain against target cluster")
if m.validator.IsValid(context.Background(), d.Domain, []string{targetCluster}) {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"accountID": accountID, "accountID": accountID,
"domainID": domainID, "domainID": domainID,
@@ -161,11 +184,11 @@ func (m Manager) ValidateDomain(accountID, domainID string) {
} }
} else { } else {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"accountID": accountID, "accountID": accountID,
"domainID": domainID, "domainID": domainID,
"domain": d.Domain, "domain": d.Domain,
"proxyAllowList": allowList, "targetCluster": targetCluster,
}).Warn("domain validation failed - CNAME does not match any connected proxy") }).Warn("domain validation failed - CNAME does not match target cluster")
} }
} }
+7 -6
View File
@@ -4845,13 +4845,14 @@ func (s *SqlStore) ListCustomDomains(ctx context.Context, accountID string) ([]*
return domains, nil return domains, nil
} }
func (s *SqlStore) CreateCustomDomain(ctx context.Context, accountID string, domainName string, validated bool) (*domain.Domain, error) { func (s *SqlStore) CreateCustomDomain(ctx context.Context, accountID string, domainName string, targetCluster string, validated bool) (*domain.Domain, error) {
newDomain := &domain.Domain{ newDomain := &domain.Domain{
ID: xid.New().String(), // Generate our own ID because gorm doesn't always configure the database to handle this for us. ID: xid.New().String(), // Generate our own ID because gorm doesn't always configure the database to handle this for us.
Domain: domainName, Domain: domainName,
AccountID: accountID, AccountID: accountID,
Type: domain.TypeCustom, TargetCluster: targetCluster,
Validated: validated, Type: domain.TypeCustom,
Validated: validated,
} }
result := s.db.Create(newDomain) result := s.db.Create(newDomain)
if result.Error != nil { if result.Error != nil {
+1 -1
View File
@@ -255,7 +255,7 @@ type Store interface {
GetCustomDomain(ctx context.Context, accountID string, domainID string) (*domain.Domain, error) GetCustomDomain(ctx context.Context, accountID string, domainID string) (*domain.Domain, error)
ListFreeDomains(ctx context.Context, accountID string) ([]string, error) ListFreeDomains(ctx context.Context, accountID string) ([]string, error)
ListCustomDomains(ctx context.Context, accountID string) ([]*domain.Domain, error) ListCustomDomains(ctx context.Context, accountID string) ([]*domain.Domain, error)
CreateCustomDomain(ctx context.Context, accountID string, domainName string, validated bool) (*domain.Domain, error) CreateCustomDomain(ctx context.Context, accountID string, domainName string, targetCluster string, validated bool) (*domain.Domain, error)
UpdateCustomDomain(ctx context.Context, accountID string, d *domain.Domain) (*domain.Domain, error) UpdateCustomDomain(ctx context.Context, accountID string, d *domain.Domain) (*domain.Domain, error)
DeleteCustomDomain(ctx context.Context, accountID string, domainID string) error DeleteCustomDomain(ctx context.Context, accountID string, domainID string) error
+7
View File
@@ -3047,6 +3047,9 @@ components:
description: Whether the domain has been validated description: Whether the domain has been validated
type: type:
$ref: '#/components/schemas/ReverseProxyDomainType' $ref: '#/components/schemas/ReverseProxyDomainType'
target_cluster:
type: string
description: The proxy cluster this domain is validated against (only for custom domains)
required: required:
- id - id
- domain - domain
@@ -3058,8 +3061,12 @@ components:
domain: domain:
type: string type: string
description: Domain name description: Domain name
target_cluster:
type: string
description: The proxy cluster this domain should be validated against
required: required:
- domain - domain
- target_cluster
InstanceStatus: InstanceStatus:
type: object type: object
description: Instance status information description: Instance status information
+6
View File
@@ -2006,6 +2006,9 @@ type ReverseProxyDomain struct {
// Id Domain ID // Id Domain ID
Id string `json:"id"` Id string `json:"id"`
// TargetCluster The proxy cluster this domain is validated against (only for custom domains)
TargetCluster *string `json:"target_cluster,omitempty"`
// Type Type of Reverse Proxy Domain // Type Type of Reverse Proxy Domain
Type ReverseProxyDomainType `json:"type"` Type ReverseProxyDomainType `json:"type"`
@@ -2017,6 +2020,9 @@ type ReverseProxyDomain struct {
type ReverseProxyDomainRequest struct { type ReverseProxyDomainRequest struct {
// Domain Domain name // Domain Domain name
Domain string `json:"domain"` Domain string `json:"domain"`
// TargetCluster The proxy cluster this domain should be validated against
TargetCluster string `json:"target_cluster"`
} }
// ReverseProxyDomainType Type of Reverse Proxy Domain // ReverseProxyDomainType Type of Reverse Proxy Domain