Skip to content

Commit 4da8ee7

Browse files
Steven Armstrongmurali-reddy
authored andcommitted
[RFC] prevent host services from being accessible through service IPs (#618)
* prevent host services from being accessible through service IPs - on startup create ipsets and firewall rules - on sync update ipsets - on cleanup remove firewall rules and ipsets Fixes #282. Signed-off-by: Steven Armstrong <[email protected]> * ensure iptables rules are also available during cleanup Signed-off-by: Steven Armstrong <[email protected]> * first check if chain exists Signed-off-by: Steven Armstrong <[email protected]> * err not a new variable Signed-off-by: Steven Armstrong <[email protected]> * more redeclared vars Signed-off-by: Steven Armstrong <[email protected]> * maintain a ipset for local addresses and exclude those from our default deny rule Signed-off-by: Steven Armstrong <[email protected]> * copy/paste errors Signed-off-by: Steven Armstrong <[email protected]>
1 parent 4efc6cc commit 4da8ee7

File tree

1 file changed

+202
-22
lines changed

1 file changed

+202
-22
lines changed

pkg/controllers/proxy/network_services_controller.go

Lines changed: 202 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ const (
5656
svcSchedFlagsAnnotation = "kube-router.io/service.schedflags"
5757

5858
LeaderElectionRecordAnnotationKey = "control-plane.alpha.kubernetes.io/leader"
59-
svcIpSetName = "KUBE-SVC-ALL"
59+
localIPsIPSetName = "kube-router-local-ips"
60+
ipvsServicesIPSetName = "kube-router-ipvs-services"
61+
serviceIPsIPSetName = "kube-router-service-ips"
62+
ipvsFirewallChainName = "KUBE-ROUTER-SERVICES"
6063
)
6164

6265
var (
@@ -214,6 +217,9 @@ type NetworkServicesController struct {
214217
ln LinuxNetworking
215218
readyForUpdates bool
216219

220+
// Map of ipsets that we use.
221+
ipsetMap map[string]*utils.Set
222+
217223
svcLister cache.Indexer
218224
epLister cache.Indexer
219225
podLister cache.Indexer
@@ -318,6 +324,12 @@ func (nsc *NetworkServicesController) Run(healthChan chan<- *healthcheck.Control
318324
return errors.New(sysctlErr.Error())
319325
}
320326

327+
// https://github.com/cloudnativelabs/kube-router/issues/282
328+
err = nsc.setupIpvsFirewall()
329+
if err != nil {
330+
return errors.New("Error setting up ipvs firewall: " + err.Error())
331+
}
332+
321333
// loop forever unitl notified to stop on stopCh
322334
for {
323335
select {
@@ -375,52 +387,110 @@ func (nsc *NetworkServicesController) sync() error {
375387
return nil
376388
}
377389

390+
func getIpvsFirewallInputChainRule() []string {
391+
// The iptables rule for use in {setup,cleanup}IpvsFirewall.
392+
return []string{
393+
"-m", "comment", "--comment", "handle traffic to IPVS service IPs in custom chain",
394+
"-m", "set", "--match-set", serviceIPsIPSetName, "dst",
395+
"-j", ipvsFirewallChainName}
396+
}
397+
378398
func (nsc *NetworkServicesController) setupIpvsFirewall() error {
379-
// Add ipset containg all SVCs
399+
/*
400+
- create ipsets
401+
- create firewall rules
402+
*/
403+
404+
var err error
405+
var ipset *utils.Set
406+
380407
ipSetHandler, err := utils.NewIPSet(false)
381408
if err != nil {
382409
return err
383410
}
384411

385-
svcIpSet, err := ipSetHandler.Create(svcIpSetName, utils.TypeHashIPPort, utils.OptionTimeout, "0")
412+
// Remember ipsets for use in syncIpvsFirewall
413+
nsc.ipsetMap = make(map[string]*utils.Set)
414+
415+
// Create ipset for local addresses.
416+
ipset, err = ipSetHandler.Create(localIPsIPSetName, utils.TypeHashIP, utils.OptionTimeout, "0")
386417
if err != nil {
387418
return fmt.Errorf("failed to create ipset: %s", err.Error())
388419
}
420+
nsc.ipsetMap[localIPsIPSetName] = ipset
389421

390-
ipvsSvcs, err := nsc.ln.ipvsGetServices()
422+
// Create 2 ipsets for services. One for 'ip' and one for 'ip,port'
423+
ipset, err = ipSetHandler.Create(serviceIPsIPSetName, utils.TypeHashIP, utils.OptionTimeout, "0")
391424
if err != nil {
392-
return errors.New("Failed to list IPVS services: " + err.Error())
425+
return fmt.Errorf("failed to create ipset: %s", err.Error())
393426
}
427+
nsc.ipsetMap[serviceIPsIPSetName] = ipset
394428

395-
svcSets := make([]string, 0, len(ipvsSvcs))
396-
for _, ipvsSvc := range ipvsSvcs {
397-
protocol := "udp"
398-
if ipvsSvc.Protocol == syscall.IPPROTO_TCP {
399-
protocol = "tcp"
429+
ipset, err = ipSetHandler.Create(ipvsServicesIPSetName, utils.TypeHashIPPort, utils.OptionTimeout, "0")
430+
if err != nil {
431+
return fmt.Errorf("failed to create ipset: %s", err.Error())
432+
}
433+
nsc.ipsetMap[ipvsServicesIPSetName] = ipset
434+
435+
// Setup a custom iptables chain to explicitly allow input traffic to
436+
// ipvs services only.
437+
iptablesCmdHandler, err := iptables.New()
438+
if err != nil {
439+
return errors.New("Failed to initialize iptables executor" + err.Error())
440+
}
441+
442+
// ClearChain either clears an existing chain or creates a new one.
443+
err = iptablesCmdHandler.ClearChain("filter", ipvsFirewallChainName)
444+
if err != nil {
445+
return fmt.Errorf("Failed to run iptables command: %s", err.Error())
446+
}
447+
448+
var comment string
449+
var args []string
450+
451+
comment = "allow input traffic to ipvs services"
452+
args = []string{"-m", "comment", "--comment", comment,
453+
"-m", "set", "--match-set", ipvsServicesIPSetName, "dst,dst",
454+
"-j", "ACCEPT"}
455+
exists, err := iptablesCmdHandler.Exists("filter", ipvsFirewallChainName, args...)
456+
if err != nil {
457+
return fmt.Errorf("Failed to run iptables command: %s", err.Error())
458+
}
459+
if !exists {
460+
err := iptablesCmdHandler.Insert("filter", ipvsFirewallChainName, 1, args...)
461+
if err != nil {
462+
return fmt.Errorf("Failed to run iptables command: %s", err.Error())
400463
}
401-
set := fmt.Sprintf("%s,%s:%d", ipvsSvc.Address.String(), protocol, ipvsSvc.Port)
402-
svcSets = append(svcSets, set)
403464
}
404465

405-
err = svcIpSet.Refresh(svcSets, utils.OptionTimeout, "0")
466+
comment = "allow icmp echo requests to service IPs"
467+
args = []string{"-m", "comment", "--comment", comment,
468+
"-p", "icmp", "--icmp-type", "echo-request",
469+
"-j", "ACCEPT"}
470+
err = iptablesCmdHandler.AppendUnique("filter", ipvsFirewallChainName, args...)
406471
if err != nil {
407-
return fmt.Errorf("failed to sync ipset: %s", err.Error())
472+
return fmt.Errorf("Failed to run iptables command: %s", err.Error())
408473
}
409474

410-
// Add iptables rule to allow input traffic to ipvs services
411-
iptablesCmdHandler, err := iptables.New()
475+
// We exclude the local addresses here as that would otherwise block all
476+
// traffic to local addresses if any NodePort service exists.
477+
comment = "reject all unexpected traffic to service IPs"
478+
args = []string{"-m", "comment", "--comment", comment,
479+
"-m", "set", "!", "--match-set", localIPsIPSetName, "dst",
480+
"-j", "REJECT", "--reject-with", "icmp-port-unreachable"}
481+
err = iptablesCmdHandler.AppendUnique("filter", ipvsFirewallChainName, args...)
412482
if err != nil {
413-
return errors.New("Failed to initialize iptables executor" + err.Error())
483+
return fmt.Errorf("Failed to run iptables command: %s", err.Error())
414484
}
415485

416-
comment := "allow input traffic to ipvs services"
417-
args := []string{"-m", "comment", "--comment", comment, "-m", "set", "--match-set", svcIpSetName, "dst,dst", "-j", "ACCEPT"}
418-
exists, err := iptablesCmdHandler.Exists("filter", "INPUT", args...)
486+
// Pass incomming traffic into our custom chain.
487+
ipvsFirewallInputChainRule := getIpvsFirewallInputChainRule()
488+
exists, err = iptablesCmdHandler.Exists("filter", "INPUT", ipvsFirewallInputChainRule...)
419489
if err != nil {
420490
return fmt.Errorf("Failed to run iptables command: %s", err.Error())
421491
}
422492
if !exists {
423-
err := iptablesCmdHandler.Insert("filter", "INPUT", 1, args...)
493+
err = iptablesCmdHandler.Insert("filter", "INPUT", 1, ipvsFirewallInputChainRule...)
424494
if err != nil {
425495
return fmt.Errorf("Failed to run iptables command: %s", err.Error())
426496
}
@@ -429,6 +499,114 @@ func (nsc *NetworkServicesController) setupIpvsFirewall() error {
429499
return nil
430500
}
431501

502+
func (nsc *NetworkServicesController) cleanupIpvsFirewall() {
503+
/*
504+
- delete firewall rules
505+
- delete ipsets
506+
*/
507+
var err error
508+
509+
// Clear iptables rules.
510+
iptablesCmdHandler, err := iptables.New()
511+
if err != nil {
512+
glog.Errorf("Failed to initialize iptables executor: %s", err.Error())
513+
} else {
514+
ipvsFirewallInputChainRule := getIpvsFirewallInputChainRule()
515+
err = iptablesCmdHandler.Delete("filter", "INPUT", ipvsFirewallInputChainRule...)
516+
if err != nil {
517+
glog.Errorf("Failed to run iptables command: %s", err.Error())
518+
}
519+
520+
err = iptablesCmdHandler.ClearChain("filter", ipvsFirewallChainName)
521+
if err != nil {
522+
glog.Errorf("Failed to run iptables command: %s", err.Error())
523+
}
524+
525+
err = iptablesCmdHandler.DeleteChain("filter", ipvsFirewallChainName)
526+
if err != nil {
527+
glog.Errorf("Failed to run iptables command: %s", err.Error())
528+
}
529+
}
530+
531+
// Clear ipsets.
532+
ipSetHandler, err := utils.NewIPSet(false)
533+
if err != nil {
534+
glog.Errorf("Failed to initialize ipset handler: %s", err.Error())
535+
} else {
536+
err = ipSetHandler.Destroy(localIPsIPSetName)
537+
if err != nil {
538+
glog.Errorf("failed to destroy ipset: %s", err.Error())
539+
}
540+
541+
err = ipSetHandler.Destroy(serviceIPsIPSetName)
542+
if err != nil {
543+
glog.Errorf("failed to destroy ipset: %s", err.Error())
544+
}
545+
546+
err = ipSetHandler.Destroy(ipvsServicesIPSetName)
547+
if err != nil {
548+
glog.Errorf("failed to destroy ipset: %s", err.Error())
549+
}
550+
}
551+
}
552+
553+
func (nsc *NetworkServicesController) syncIpvsFirewall() error {
554+
/*
555+
- update ipsets based on currently active IPVS services
556+
*/
557+
var err error
558+
559+
localIPsIPSet := nsc.ipsetMap[localIPsIPSetName]
560+
561+
// Populate local addresses ipset.
562+
addrs, err := getAllLocalIPs()
563+
localIPsSets := make([]string, 0, len(addrs))
564+
for _, addr := range addrs {
565+
localIPsSets = append(localIPsSets, addr.IP.String())
566+
}
567+
err = localIPsIPSet.Refresh(localIPsSets, utils.OptionTimeout, "0")
568+
if err != nil {
569+
return fmt.Errorf("failed to sync ipset: %s", err.Error())
570+
}
571+
572+
// Populate service ipsets.
573+
ipvsServices, err := nsc.ln.ipvsGetServices()
574+
if err != nil {
575+
return errors.New("Failed to list IPVS services: " + err.Error())
576+
}
577+
578+
serviceIPsSets := make([]string, 0, len(ipvsServices))
579+
ipvsServicesSets := make([]string, 0, len(ipvsServices))
580+
581+
for _, ipvsService := range ipvsServices {
582+
protocol := "udp"
583+
if ipvsService.Protocol == syscall.IPPROTO_TCP {
584+
protocol = "tcp"
585+
}
586+
587+
serviceIPsSet := ipvsService.Address.String()
588+
serviceIPsSets = append(serviceIPsSets, serviceIPsSet)
589+
590+
ipvsServicesSet := fmt.Sprintf("%s,%s:%d", ipvsService.Address.String(), protocol, ipvsService.Port)
591+
ipvsServicesSets = append(ipvsServicesSets, ipvsServicesSet)
592+
593+
}
594+
595+
serviceIPsIPSet := nsc.ipsetMap[serviceIPsIPSetName]
596+
err = serviceIPsIPSet.Refresh(serviceIPsSets, utils.OptionTimeout, "0")
597+
if err != nil {
598+
return fmt.Errorf("failed to sync ipset: %s", err.Error())
599+
}
600+
601+
ipvsServicesIPSet := nsc.ipsetMap[ipvsServicesIPSetName]
602+
err = ipvsServicesIPSet.Refresh(ipvsServicesSets, utils.OptionTimeout, "0")
603+
if err != nil {
604+
return fmt.Errorf("failed to sync ipset: %s", err.Error())
605+
}
606+
607+
return nil
608+
}
609+
432610
func (nsc *NetworkServicesController) publishMetrics(serviceInfoMap serviceInfoMap) error {
433611
start := time.Now()
434612
defer func() {
@@ -952,7 +1130,7 @@ func (nsc *NetworkServicesController) syncIpvsServices(serviceInfoMap serviceInf
9521130
}
9531131
}
9541132

955-
err = nsc.setupIpvsFirewall()
1133+
err = nsc.syncIpvsFirewall()
9561134
if err != nil {
9571135
glog.Errorf("Error syncing ipvs svc iptable rules: %s", err.Error())
9581136
}
@@ -2061,6 +2239,8 @@ func (nsc *NetworkServicesController) Cleanup() {
20612239
return
20622240
}
20632241

2242+
nsc.cleanupIpvsFirewall()
2243+
20642244
// delete dummy interface used to assign cluster IP's
20652245
dummyVipInterface, err := netlink.LinkByName(KUBE_DUMMY_IF)
20662246
if err != nil {

0 commit comments

Comments
 (0)