github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/firewall/incoming_firewall_iptables.go (about)

     1  /*
     2   * Copyright (C) 2020 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package firewall
    19  
    20  import (
    21  	"net"
    22  	"net/url"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/mysteriumnetwork/node/firewall/ipset"
    27  	"github.com/mysteriumnetwork/node/firewall/iptables"
    28  	"github.com/rs/zerolog/log"
    29  )
    30  
    31  const (
    32  	incomingFirewallChain = "MYST_PROVIDER_FIREWALL"
    33  	incomingFirewallIpset = "myst-provider-dst-whitelist"
    34  )
    35  
    36  // incomingFirewallIptables allows incoming traffic blocking in IP granularity.
    37  type incomingFirewallIptables struct{}
    38  
    39  func (ibi *incomingFirewallIptables) Setup() error {
    40  	if err := ibi.checkIpsetVersion(); err != nil {
    41  		return err
    42  	}
    43  
    44  	// Clean up setups from previous runs, just in case
    45  	if err := ibi.cleanupStaleRules(); err != nil {
    46  		return err
    47  	}
    48  	ipset.Exec(ipset.OpDelete(incomingFirewallIpset))
    49  
    50  	op := ipset.OpCreate(incomingFirewallIpset, ipset.SetTypeHashIP, 24*time.Hour, nil, 0)
    51  	if _, err := ipset.Exec(op); err != nil {
    52  		return err
    53  	}
    54  	return ibi.setupFirewallChain()
    55  }
    56  
    57  func (ibi *incomingFirewallIptables) Teardown() {
    58  	if err := ibi.cleanupStaleRules(); err != nil {
    59  		log.Warn().Err(err).Msg("Error cleaning up iptables rules, you might want to do it yourself")
    60  	}
    61  	if errOutput, err := ipset.Exec(ipset.OpDelete(incomingFirewallIpset)); err != nil {
    62  		log.Warn().Err(err).Msgf("Error deleting ipset table. %s", strings.Join(errOutput, ""))
    63  	}
    64  }
    65  
    66  func (ibi *incomingFirewallIptables) BlockIncomingTraffic(network net.IPNet) (IncomingRuleRemove, error) {
    67  	remover, err := iptables.AddRuleWithRemoval(
    68  		iptables.AppendTo("FORWARD").RuleSpec("-s", network.String(), "-j", incomingFirewallChain),
    69  	)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	return func() error {
    74  		remover()
    75  		return nil
    76  	}, nil
    77  }
    78  
    79  // AllowURLAccess adds URL based exception.
    80  func (ibi *incomingFirewallIptables) AllowURLAccess(rawURLs ...string) (IncomingRuleRemove, error) {
    81  	var ruleRemovers []func()
    82  	removeAll := func() error {
    83  		for _, ruleRemover := range ruleRemovers {
    84  			ruleRemover()
    85  		}
    86  		return nil
    87  	}
    88  
    89  	for _, rawURL := range rawURLs {
    90  		parsed, err := url.Parse(rawURL)
    91  		if err != nil {
    92  			removeAll()
    93  			return nil, err
    94  		}
    95  
    96  		remover, err := iptables.AddRuleWithRemoval(
    97  			iptables.InsertAt(incomingFirewallChain, 1).RuleSpec("-d", parsed.Hostname(), "-j", "ACCEPT"),
    98  		)
    99  		if err != nil {
   100  			removeAll()
   101  			return nil, err
   102  		}
   103  		ruleRemovers = append(ruleRemovers, remover)
   104  	}
   105  	return removeAll, nil
   106  }
   107  
   108  func (ibi *incomingFirewallIptables) AllowIPAccess(ip net.IP) (IncomingRuleRemove, error) {
   109  	if _, err := ipset.Exec(ipset.OpIPAdd(incomingFirewallIpset, ip, true)); err != nil {
   110  		return nil, err
   111  	}
   112  	return func() error {
   113  		_, err := ipset.Exec(ipset.OpIPRemove(incomingFirewallIpset, ip))
   114  		return err
   115  	}, nil
   116  }
   117  
   118  func (ibi *incomingFirewallIptables) checkIpsetVersion() error {
   119  	output, err := ipset.Exec(ipset.OpVersion())
   120  	if err != nil {
   121  		return err
   122  	}
   123  	for _, line := range output {
   124  		log.Info().Msg("[version check] " + line)
   125  	}
   126  	return nil
   127  }
   128  
   129  func (ibi *incomingFirewallIptables) setupFirewallChain() error {
   130  	// Add chain
   131  	if _, err := iptables.Exec("-N", incomingFirewallChain); err != nil {
   132  		return err
   133  	}
   134  
   135  	// Append rule - packets going to firewall with these destination IPs are whitelisted
   136  	if _, err := iptables.Exec("-A", incomingFirewallChain, "-m", "set", "--match-set", incomingFirewallIpset, "dst", "-j", "ACCEPT"); err != nil {
   137  		return err
   138  	}
   139  
   140  	// Append rule - by default all packets going to firewall chain are rejected
   141  	if _, err := iptables.Exec("-A", incomingFirewallChain, "-j", "REJECT"); err != nil {
   142  		return err
   143  	}
   144  
   145  	return nil
   146  }
   147  
   148  func (ibi *incomingFirewallIptables) cleanupStaleRules() error {
   149  	// List rules
   150  	rules, err := iptables.Exec("-S", "FORWARD")
   151  	if err != nil {
   152  		return err
   153  	}
   154  	for _, rule := range rules {
   155  		// detect if any references exist in FORWARD chain like -j MYST_PROVIDER_FIREWALL
   156  		if strings.HasSuffix(rule, incomingFirewallChain) {
   157  			deleteRule := strings.Replace(rule, "-A", "-D", 1)
   158  			deleteRuleArgs := strings.Split(deleteRule, " ")
   159  			if _, err := iptables.Exec(deleteRuleArgs...); err != nil {
   160  				return err
   161  			}
   162  		}
   163  	}
   164  
   165  	// List chain rules
   166  	if _, err := iptables.Exec("-L", incomingFirewallChain); err != nil {
   167  		// error means no such chain - log error just in case and bail out
   168  		log.Info().Err(err).Msg("[setup] Got error while listing kill switch chain rules. Probably nothing to worry about")
   169  		return nil
   170  	}
   171  
   172  	// Remove chain rules
   173  	if _, err := iptables.Exec("-F", incomingFirewallChain); err != nil {
   174  		return err
   175  	}
   176  
   177  	// Remove chain
   178  	_, err = iptables.Exec("-X", incomingFirewallChain)
   179  	return err
   180  }
   181  
   182  var _ IncomingTrafficFirewall = &incomingFirewallIptables{}