Skip to content

Commit 9e01961

Browse files
authored
NM-79: Domain Based Egress Routing (#3607)
* add support for egress domain routing * add domain info to egress range * fix egress domain update * send peer update domain resolution update * add egress domain update in the peer update * use range field for domain check * add egress domain to host pull * add egress domain model to egress host update * add egress domain model to egress host update * update egress domain model on acls * add check of range if domain is set * sync egress domains to dns system * add egress domain to match domain list, fix egress nat rule for domains * fix all rsrcs comms * fix static checks * fix egress acls on CE * check for all resources access on a node * simplify egress acl rules * merged ce and pro acl rule func * fix uni direction acl rule for static nodes * allow relayed nodes traffic * resolve merge conflicts * remove anywhere dst rule on user node acls * fix: broadcast user groups update for acl changes * add egress domain ans routes to nodes * add egress ranges to DST * add all egress ranges for all resources * fix DNS routing acls rules
1 parent 57bf34d commit 9e01961

File tree

19 files changed

+674
-165
lines changed

19 files changed

+674
-165
lines changed

controllers/egress.go

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,27 @@ func createEgress(w http.ResponseWriter, r *http.Request) {
4545
return
4646
}
4747
var egressRange string
48+
var cidrErr error
4849
if !req.IsInetGw {
49-
egressRange, err = logic.NormalizeCIDR(req.Range)
50-
if err != nil {
51-
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
50+
if req.Range != "" {
51+
egressRange, cidrErr = logic.NormalizeCIDR(req.Range)
52+
}
53+
isDomain := logic.IsFQDN(req.Range)
54+
if cidrErr != nil && !isDomain {
55+
if cidrErr != nil {
56+
logic.ReturnErrorResponse(w, r, logic.FormatError(cidrErr, "badrequest"))
57+
} else {
58+
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("bad domain name"), "badrequest"))
59+
}
5260
return
5361
}
62+
if isDomain {
63+
req.Domain = req.Range
64+
egressRange = ""
65+
}
5466
} else {
5567
egressRange = "*"
68+
req.Domain = ""
5669
}
5770

5871
e := schema.Egress{
@@ -61,6 +74,8 @@ func createEgress(w http.ResponseWriter, r *http.Request) {
6174
Network: req.Network,
6275
Description: req.Description,
6376
Range: egressRange,
77+
Domain: req.Domain,
78+
DomainAns: []string{},
6479
Nat: req.Nat,
6580
Nodes: make(datatypes.JSONMap),
6681
Tags: make(datatypes.JSONMap),
@@ -108,7 +123,35 @@ func createEgress(w http.ResponseWriter, r *http.Request) {
108123
// }
109124

110125
// }
111-
go mq.PublishPeerUpdate(false)
126+
if req.Domain != "" {
127+
if req.Nodes != nil {
128+
for nodeID := range req.Nodes {
129+
node, err := logic.GetNodeByID(nodeID)
130+
if err != nil {
131+
continue
132+
}
133+
host, _ := logic.GetHost(node.HostID.String())
134+
if host == nil {
135+
continue
136+
}
137+
mq.HostUpdate(&models.HostUpdate{
138+
Action: models.EgressUpdate,
139+
Host: *host,
140+
EgressDomain: models.EgressDomain{
141+
ID: e.ID,
142+
Host: *host,
143+
Node: node,
144+
Domain: e.Domain,
145+
},
146+
Node: node,
147+
})
148+
}
149+
}
150+
151+
} else {
152+
go mq.PublishPeerUpdate(false)
153+
}
154+
112155
logic.ReturnSuccessResponseWithJson(w, r, e, "created egress resource")
113156
}
114157

@@ -161,14 +204,25 @@ func updateEgress(w http.ResponseWriter, r *http.Request) {
161204
return
162205
}
163206
var egressRange string
207+
var cidrErr error
164208
if !req.IsInetGw {
165-
egressRange, err = logic.NormalizeCIDR(req.Range)
166-
if err != nil {
167-
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
209+
egressRange, cidrErr = logic.NormalizeCIDR(req.Range)
210+
isDomain := logic.IsFQDN(req.Range)
211+
if cidrErr != nil && !isDomain {
212+
if cidrErr != nil {
213+
logic.ReturnErrorResponse(w, r, logic.FormatError(cidrErr, "badrequest"))
214+
} else {
215+
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("bad domain name"), "badrequest"))
216+
}
168217
return
169218
}
219+
if isDomain {
220+
req.Domain = req.Range
221+
egressRange = ""
222+
}
170223
} else {
171224
egressRange = "*"
225+
req.Domain = ""
172226
}
173227

174228
e := schema.Egress{ID: req.ID}
@@ -209,10 +263,14 @@ func updateEgress(w http.ResponseWriter, r *http.Request) {
209263
for nodeID, metric := range req.Nodes {
210264
e.Nodes[nodeID] = metric
211265
}
266+
if e.Domain != req.Domain {
267+
e.DomainAns = datatypes.JSONSlice[string]{}
268+
}
212269
e.Range = egressRange
213270
e.Description = req.Description
214271
e.Name = req.Name
215272
e.Nat = req.Nat
273+
e.Domain = req.Domain
216274
e.Status = req.Status
217275
e.UpdatedAt = time.Now().UTC()
218276
if err := logic.ValidateEgressReq(&e); err != nil {
@@ -238,6 +296,34 @@ func updateEgress(w http.ResponseWriter, r *http.Request) {
238296
}
239297
event.Diff.New = e
240298
logic.LogEvent(event)
299+
if req.Domain != "" {
300+
if req.Nodes != nil {
301+
for nodeID := range req.Nodes {
302+
node, err := logic.GetNodeByID(nodeID)
303+
if err != nil {
304+
continue
305+
}
306+
host, _ := logic.GetHost(node.HostID.String())
307+
if host == nil {
308+
continue
309+
}
310+
mq.HostUpdate(&models.HostUpdate{
311+
Action: models.EgressUpdate,
312+
Host: *host,
313+
EgressDomain: models.EgressDomain{
314+
ID: e.ID,
315+
Host: *host,
316+
Node: node,
317+
Domain: e.Domain,
318+
},
319+
Node: node,
320+
})
321+
}
322+
}
323+
324+
} else {
325+
go mq.PublishPeerUpdate(false)
326+
}
241327
go mq.PublishPeerUpdate(false)
242328
logic.ReturnSuccessResponseWithJson(w, r, e, "updated egress resource")
243329
}

controllers/hosts.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,13 @@ func pull(w http.ResponseWriter, r *http.Request) {
253253
ChangeDefaultGw: hPU.ChangeDefaultGw,
254254
DefaultGwIp: hPU.DefaultGwIp,
255255
IsInternetGw: hPU.IsInternetGw,
256+
NameServers: hPU.NameServers,
257+
EgressWithDomains: hPU.EgressWithDomains,
256258
EndpointDetection: logic.IsEndpointDetectionEnabled(),
257259
DnsNameservers: hPU.DnsNameservers,
258260
}
259261

260-
logger.Log(1, hostID, "completed a pull")
262+
logger.Log(1, hostID, host.Name, "completed a pull")
261263
w.WriteHeader(http.StatusOK)
262264
json.NewEncoder(w).Encode(&response)
263265
}
@@ -374,7 +376,6 @@ func hostUpdateFallback(w http.ResponseWriter, r *http.Request) {
374376
switch hostUpdate.Action {
375377
case models.CheckIn:
376378
sendPeerUpdate = mq.HandleHostCheckin(&hostUpdate.Host, currentHost)
377-
378379
case models.UpdateHost:
379380
if hostUpdate.Host.PublicKey != currentHost.PublicKey {
380381
//remove old peer entry
@@ -384,12 +385,24 @@ func hostUpdateFallback(w http.ResponseWriter, r *http.Request) {
384385
err := logic.UpsertHost(currentHost)
385386
if err != nil {
386387
slog.Error("failed to update host", "id", currentHost.ID, "error", err)
387-
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
388+
logic.ReturnErrorResponse(w, r, logic.FormatError(err, logic.Internal))
388389
return
389390
}
390391

391392
case models.UpdateMetrics:
392393
mq.UpdateMetricsFallBack(hostUpdate.Node.ID.String(), hostUpdate.NewMetrics)
394+
case models.EgressUpdate:
395+
e := schema.Egress{ID: hostUpdate.EgressDomain.ID}
396+
err = e.Get(db.WithContext(r.Context()))
397+
if err != nil {
398+
logic.ReturnErrorResponse(w, r, logic.FormatError(err, logic.BadReq))
399+
return
400+
}
401+
if len(hostUpdate.Node.EgressGatewayRanges) > 0 {
402+
e.DomainAns = hostUpdate.Node.EgressGatewayRanges
403+
e.Update(db.WithContext(r.Context()))
404+
}
405+
sendPeerUpdate = true
393406
}
394407

395408
if sendPeerUpdate {

logic/acls.go

Lines changed: 85 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
5454
if !nodeI.IsStatic || nodeI.IsUserNode {
5555
continue
5656
}
57+
if !node.StaticNode.Enabled {
58+
continue
59+
}
5760
// if nodeI.StaticNode.IngressGatewayID != node.ID.String() {
5861
// continue
5962
// }
@@ -292,35 +295,70 @@ func getFwRulesForNodeAndPeerOnGw(node, peer models.Node, allowedPolicies []mode
292295
if err != nil {
293296
continue
294297
}
295-
dstI.Value = e.Range
298+
if len(e.DomainAns) > 0 {
299+
for _, domainAnsI := range e.DomainAns {
300+
dstI.Value = domainAnsI
301+
302+
ip, cidr, err := net.ParseCIDR(dstI.Value)
303+
if err == nil {
304+
if ip.To4() != nil {
305+
if node.Address.IP != nil {
306+
rules = append(rules, models.FwRule{
307+
SrcIP: net.IPNet{
308+
IP: node.Address.IP,
309+
Mask: net.CIDRMask(32, 32),
310+
},
311+
DstIP: *cidr,
312+
Allow: true,
313+
})
314+
}
315+
} else {
316+
if node.Address6.IP != nil {
317+
rules = append(rules, models.FwRule{
318+
SrcIP: net.IPNet{
319+
IP: node.Address6.IP,
320+
Mask: net.CIDRMask(128, 128),
321+
},
322+
DstIP: *cidr,
323+
Allow: true,
324+
})
325+
}
326+
}
296327

297-
ip, cidr, err := net.ParseCIDR(dstI.Value)
298-
if err == nil {
299-
if ip.To4() != nil {
300-
if node.Address.IP != nil {
301-
rules = append(rules, models.FwRule{
302-
SrcIP: net.IPNet{
303-
IP: node.Address.IP,
304-
Mask: net.CIDRMask(32, 32),
305-
},
306-
DstIP: *cidr,
307-
Allow: true,
308-
})
309-
}
310-
} else {
311-
if node.Address6.IP != nil {
312-
rules = append(rules, models.FwRule{
313-
SrcIP: net.IPNet{
314-
IP: node.Address6.IP,
315-
Mask: net.CIDRMask(128, 128),
316-
},
317-
DstIP: *cidr,
318-
Allow: true,
319-
})
320328
}
321329
}
330+
} else {
331+
dstI.Value = e.Range
322332

333+
ip, cidr, err := net.ParseCIDR(dstI.Value)
334+
if err == nil {
335+
if ip.To4() != nil {
336+
if node.Address.IP != nil {
337+
rules = append(rules, models.FwRule{
338+
SrcIP: net.IPNet{
339+
IP: node.Address.IP,
340+
Mask: net.CIDRMask(32, 32),
341+
},
342+
DstIP: *cidr,
343+
Allow: true,
344+
})
345+
}
346+
} else {
347+
if node.Address6.IP != nil {
348+
rules = append(rules, models.FwRule{
349+
SrcIP: net.IPNet{
350+
IP: node.Address6.IP,
351+
Mask: net.CIDRMask(128, 128),
352+
},
353+
DstIP: *cidr,
354+
Allow: true,
355+
})
356+
}
357+
}
358+
359+
}
323360
}
361+
324362
}
325363
}
326364
}
@@ -364,6 +402,9 @@ func GetStaticNodeIps(node models.Node) (ips []net.IP) {
364402
if !extclient.IsUserNode && defaultDevicePolicy.Enabled {
365403
continue
366404
}
405+
if !extclient.StaticNode.Enabled {
406+
continue
407+
}
367408
if extclient.StaticNode.Address != "" {
368409
ips = append(ips, extclient.StaticNode.AddressIPNet4().IP)
369410
}
@@ -673,7 +714,6 @@ func GetAclRulesForNode(targetnodeI *models.Node) (rules map[string]models.AclRu
673714
}
674715

675716
func GetEgressRulesForNode(targetnode models.Node) (rules map[string]models.AclRule) {
676-
fmt.Println("==========> Getting Egress FW rules ", targetnode.ID)
677717
rules = make(map[string]models.AclRule)
678718
defer func() {
679719
rules = GetEgressUserRulesForNode(&targetnode, rules)
@@ -720,14 +760,28 @@ func GetEgressRulesForNode(targetnode models.Node) (rules map[string]models.AclR
720760
}
721761
for egressID, egI := range egressIDMap {
722762
if _, ok := dstTags[egressID]; ok || dstAll {
723-
ip, cidr, err := net.ParseCIDR(egI.Range)
724-
if err == nil {
725-
if ip.To4() != nil {
726-
aclRule.Dst = append(aclRule.Dst, *cidr)
727-
} else {
728-
aclRule.Dst6 = append(aclRule.Dst6, *cidr)
763+
if servercfg.IsPro && egI.Domain != "" && len(egI.DomainAns) > 0 {
764+
for _, domainAnsI := range egI.DomainAns {
765+
ip, cidr, err := net.ParseCIDR(domainAnsI)
766+
if err == nil {
767+
if ip.To4() != nil {
768+
aclRule.Dst = append(aclRule.Dst, *cidr)
769+
} else {
770+
aclRule.Dst6 = append(aclRule.Dst6, *cidr)
771+
}
772+
}
773+
}
774+
} else {
775+
ip, cidr, err := net.ParseCIDR(egI.Range)
776+
if err == nil {
777+
if ip.To4() != nil {
778+
aclRule.Dst = append(aclRule.Dst, *cidr)
779+
} else {
780+
aclRule.Dst6 = append(aclRule.Dst6, *cidr)
781+
}
729782
}
730783
}
784+
731785
_, srcAll := srcTags["*"]
732786
if srcAll {
733787
if targetnode.NetworkRange.IP != nil {

0 commit comments

Comments
 (0)