Commit b1b0e86f authored by Alain Takoudjou's avatar Alain Takoudjou

firewalld used to restrict access between partitions (only on tap ipv4)

firewall process is now controlled by supervisord

slapos firewall: speed up computer_partitions configuration

slapos firewall: block etablished and related connection which are not allowed
parent 393bb540
...@@ -274,28 +274,19 @@ class SlapTool(BaseTool): ...@@ -274,28 +274,19 @@ class SlapTool(BaseTool):
@UnrestrictedMethod @UnrestrictedMethod
def _getHostingSubscriptionIpList(self, computer_id, computer_partition_id): def _getHostingSubscriptionIpList(self, computer_id, computer_partition_id):
def getHostingSubscriptionInstanceList(software_instance):
pred_list = []
if software_instance is None or software_instance.getSlapState() == 'destroy_requested':
return pred_list
else:
if software_instance.getPortalType() == 'Software Instance':
pred_list.append(software_instance)
predecessor_list = software_instance.getPredecessorValueList(
portal_type="Software Instance")
for instance in predecessor_list:
pred_list.extend(getHostingSubscriptionInstanceList(instance))
return pred_list
software_instance = self._getSoftwareInstanceForComputerPartition( software_instance = self._getSoftwareInstanceForComputerPartition(
computer_id, computer_partition_id) computer_id, computer_partition_id)
if software_instance is None or \
software_instance.getSlapState() == 'destroy_requested':
return xml_marshaller.xml_marshaller.dumps([])
# Search hosting subscription # Search hosting subscription
hosting = software_instance.getSpecialiseValue() hosting = software_instance.getSpecialiseValue()
while hosting and hosting.getPortalType() != "Hosting Subscription": while hosting and hosting.getPortalType() != "Hosting Subscription":
hosting = hosting.getSpecialiseValue() hosting = hosting.getSpecialiseValue()
ip_address_list = [] ip_address_list = []
for instance in getHostingSubscriptionInstanceList(hosting): for instance in hosting.getSpecialiseRelatedValueList(
portal_type="Software Instance"):
computer_partition = instance.getAggregateValue(portal_type="Computer Partition") computer_partition = instance.getAggregateValue(portal_type="Computer Partition")
if not computer_partition: if not computer_partition:
continue continue
......
...@@ -40,6 +40,7 @@ import time ...@@ -40,6 +40,7 @@ import time
import traceback import traceback
import warnings import warnings
import logging import logging
import json
if sys.version_info < (2, 6): if sys.version_info < (2, 6):
warnings.warn('Used python version (%s) is old and has problems with' warnings.warn('Used python version (%s) is old and has problems with'
...@@ -56,7 +57,7 @@ from slapos.grid.svcbackend import (launchSupervisord, ...@@ -56,7 +57,7 @@ from slapos.grid.svcbackend import (launchSupervisord,
createSupervisordConfiguration, createSupervisordConfiguration,
_getSupervisordConfigurationDirectory, _getSupervisordConfigurationDirectory,
_getSupervisordSocketPath) _getSupervisordSocketPath)
from slapos.grid.utils import (md5digest, dropPrivileges, SlapPopen) from slapos.grid.utils import (md5digest, dropPrivileges, SlapPopen, updateFile)
from slapos.human import human2bytes from slapos.human import human2bytes
import slapos.slap import slapos.slap
from netaddr import valid_ipv4, valid_ipv6 from netaddr import valid_ipv4, valid_ipv6
...@@ -164,6 +165,13 @@ def merged_options(args, configp): ...@@ -164,6 +165,13 @@ def merged_options(args, configp):
url.strip() for url in options.get( url.strip() for url in options.get(
"upload-to-binary-cache-url-blacklist", "").split('\n') if url] "upload-to-binary-cache-url-blacklist", "").split('\n') if url]
options['firewall'] = {}
if configp.has_section('firewall'):
options['firewall'] = dict(configp.items('firewall'))
options['firewall']["authorized_sources"] = [
source.strip() for source in options['firewall'].get(
"authorized_sources", "").split('\n') if source]
return options return options
...@@ -235,7 +243,8 @@ def create_slapgrid_object(options, logger): ...@@ -235,7 +243,8 @@ def create_slapgrid_object(options, logger):
software_min_free_space=software_min_free_space, software_min_free_space=software_min_free_space,
instance_min_free_space=instance_min_free_space, instance_min_free_space=instance_min_free_space,
instance_storage_home=op.get('instance_storage_home'), instance_storage_home=op.get('instance_storage_home'),
ipv4_global_network=op.get('ipv4_global_network')) ipv4_global_network=op.get('ipv4_global_network'),
firewall_conf=op.get('firewall'))
def check_required_only_partitions(existing, required): def check_required_only_partitions(existing, required):
...@@ -291,6 +300,7 @@ class Slapgrid(object): ...@@ -291,6 +300,7 @@ class Slapgrid(object):
instance_min_free_space=None, instance_min_free_space=None,
instance_storage_home=None, instance_storage_home=None,
ipv4_global_network=None, ipv4_global_network=None,
firewall_conf={},
): ):
"""Makes easy initialisation of class parameters""" """Makes easy initialisation of class parameters"""
# Parses arguments # Parses arguments
...@@ -350,6 +360,8 @@ class Slapgrid(object): ...@@ -350,6 +360,8 @@ class Slapgrid(object):
self.ipv4_global_network = ipv4_global_network self.ipv4_global_network = ipv4_global_network
else: else:
self.ipv4_global_network= "" self.ipv4_global_network= ""
self.firewall_conf = firewall_conf
def _getWatchdogLine(self): def _getWatchdogLine(self):
invocation_list = [WATCHDOG_PATH] invocation_list = [WATCHDOG_PATH]
...@@ -361,6 +373,48 @@ class Slapgrid(object): ...@@ -361,6 +373,48 @@ class Slapgrid(object):
invocation_list.append("--instance-root '%s'" % self.instance_root) invocation_list.append("--instance-root '%s'" % self.instance_root)
return ' '.join(invocation_list) return ' '.join(invocation_list)
def _generateFirewallSupervisorConf(self):
"""If firewall section is defined in slapos configuration, generate
supervisor configuration entry for firewall process.
"""
supervisord_conf_folder_path = os.path.join(self.instance_root,
'etc', 'supervisord.conf.d')
supervisord_firewall_conf = os.path.join(supervisord_conf_folder_path,
'firewall.conf')
if not self.firewall_conf:
if os.path.exists(supervisord_firewall_conf):
os.unlink(supervisord_firewall_conf)
return
supervisord_firewall_program_conf = """\
[program:firewall]
directory=/opt/slapos
command=%(firewall_executable)s
process_name=firewall
autostart=true
autorestart=true
startsecs=0
startretries=0
exitcodes=0
stopsignal=TERM
stopwaitsecs=60
user=0
group=0
serverurl=AUTO
redirect_stderr=true
stdout_logfile=%(log_file)s
stdout_logfile_maxbytes=100KB
stdout_logfile_backups=1
stderr_logfile=%(log_file)s
stderr_logfile_maxbytes=100KB
stderr_logfile_backups=1
""" % {'firewall_executable': self.firewall_conf['firewall_executable'],
'log_file': self.firewall_conf.get('log_file', '/var/log/firewall.log')}
if not os.path.exists(supervisord_conf_folder_path):
os.makedirs(supervisord_conf_folder_path)
updateFile(
supervisord_firewall_conf, supervisord_firewall_program_conf)
def checkEnvironmentAndCreateStructure(self): def checkEnvironmentAndCreateStructure(self):
"""Checks for software_root and instance_root existence, then creates """Checks for software_root and instance_root existence, then creates
needed files and directories. needed files and directories.
...@@ -370,6 +424,7 @@ class Slapgrid(object): ...@@ -370,6 +424,7 @@ class Slapgrid(object):
raise OSError('%s does not exist.' % self.software_root) raise OSError('%s does not exist.' % self.software_root)
createSupervisordConfiguration(self.instance_root, self._getWatchdogLine()) createSupervisordConfiguration(self.instance_root, self._getWatchdogLine())
self._generateFirewallSupervisorConf()
def _launchSupervisord(self): def _launchSupervisord(self):
if not self.forbid_supervisord_automatic_launch: if not self.forbid_supervisord_automatic_launch:
...@@ -531,137 +586,163 @@ class Slapgrid(object): ...@@ -531,137 +586,163 @@ class Slapgrid(object):
if not promise_present: if not promise_present:
self.logger.info("No promise.") self.logger.info("No promise.")
def _checkAddFirewallRules(self, command_list, check_command_list, add=True): def _checkAddFirewallRules(self, partition_id, command_list, add=True):
""" """
""" """
size = len(command_list) instance_path = os.path.join(self.instance_root, partition_id)
for i in range(0, size): firewall_rules = os.path.join(instance_path, '.slapos-firewalld-rules')
command = command_list.pop() json_list = []
check_command = check_command_list.pop()
# Check if this rule exists in iptables if os.path.exists(firewall_rules):
check_result = subprocess.call(check_command) with open(firewall_rules, 'r') as frules:
self.logger.info(check_command) rules_list = json.loads(frules.read())
self.logger.info(' '.join(command))
if check_result and add: for command in rules_list:
# Add rule when it doesn't exist in iptables skip_check = False
subprocess.check_call(command) if add:
elif not check_result and not add: for i in range(0, len(command_list)):
# Drop rule when it exists in iptables new_cmd = command_list[i]
subprocess.check_call(command) if command == new_cmd:
json_list.append(command_list.pop(i))
def _addIPv4FirewallRules(self, ipv4, ipv4_list): skip_check = True
break
if skip_check:
continue
# Check if this rule exists in iptables
check_cmd = command.replace('--add-rule', '--query-rule')
process = subprocess.Popen(check_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True)
check_result = process.communicate()[0]
self.logger.debug('%s: %s' % (check_cmd, check_result))
if check_result.strip() == 'yes':
command = command.replace('--add-rule', '--remove-rule')
self.logger.debug(command)
cmd_process = subprocess.Popen(command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True)
result = cmd_process.communicate()[0]
if cmd_process.returncode == 1:
self.logger.warning('FirewallD: %s' % result)
if add:
for i in range(0, len(command_list)):
command = command_list.pop()
self.logger.debug(command)
cmd_process = subprocess.Popen(command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True)
result = cmd_process.communicate()[0]
if cmd_process.returncode == 1:
self.logger.warning('FirewallD: %s' % result)
json_list.append(command)
with open(firewall_rules, 'w') as frules:
frules.write(json.dumps(json_list))
# Apply changes: reload configuration
# XXX - need to check firewalld reload instead of restart
self.logger.info("Reloading firewall configuration...")
reload_cmd = ['slapos', 'node', 'restart', 'firewall']
reload_process = subprocess.Popen(reload_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
result = reload_process.communicate()[0]
if reload_process.returncode == 1:
self.logger.warning('FirewallD: %s' % result)
def _addFirewallRules(self, partition_id, ip, ip_list, ip_type='ipv4', add_rules=True):
""" """
""" """
command_list = [] if ip_type not in ['ipv4', 'ipv6', 'eb']:
check_command_list = [] raise NotImplementedError("firewall-cmd has not rules with tables %s." % ip_type)
for ip in ipv4_list:
command = 'firewall-cmd --permanent --direct --add-rule %s filter' % ip_type
cmd_list = []
for other_ip in ip_list:
# Configure INPUT rules # Configure INPUT rules
check_command_list.append(['iptables', '-C', 'INPUT', '-s', '%s' % ip, cmd_list.append('%s INPUT 0 -s %s -d %s -j ACCEPT' % (command,
'-d', '%s' % ipv4, '-j', 'ACCEPT']) other_ip, ip))
command_list.append(['iptables', '-I', 'INPUT', '-s', '%s' % ip, '-d',
'%s' % ipv4, '-j', 'ACCEPT'])
# Configure OUTPUT rules
check_command_list.append(['iptables', '-C', 'OUTPUT', '-s', '%s' % ipv4,
'-d', '%s' % ip, '-j', 'ACCEPT'])
command_list.append(['iptables', '-I', 'OUTPUT', '-s', '%s' % ipv4, '-d',
'%s' % ip, '-j', 'ACCEPT'])
# Configure FORWARD rules # Configure FORWARD rules
check_command_list.append(['iptables', '-C', 'FORWARD', '-s', '%s' % ip, cmd_list.append('%s FORWARD 0 -s %s -d %s -j ACCEPT' % (command,
'-d', '%s' % ipv4, '-j', 'ACCEPT']) other_ip, ip))
command_list.append(['iptables', '-I', 'FORWARD', '-s', '%s' % ip, '-d', # configure ESTABLISHED,RELATED rules
'%s' % ipv4, '-j', 'ACCEPT']) cmd_list.append('%s INPUT 0 -s %s -d %s -m state --state ESTABLISHED,RELATED -j ACCEPT' % (
command, other_ip, ip))
# Cancel all other INPUT requests
check_command_list.append(['iptables', '-C', 'INPUT', '-d', '%s' % ipv4,
'-j', 'DROP'])
command_list.append(['iptables', '-A', 'INPUT', '-d', '%s' % ipv4,
'-j', 'DROP'])
# Cancel all other OUTPUT requests
check_command_list.append(['iptables', '-C', 'OUTPUT', '-s', '%s' % ipv4,
'-j', 'DROP'])
command_list.append(['iptables', '-A', 'OUTPUT', '-s', '%s' % ipv4,
'-j', 'DROP'])
# Cancel all other FORWARD requests
check_command_list.append(['iptables', '-C', 'FORWARD', '-d', '%s' % ipv4,
'-j', 'DROP'])
command_list.append(['iptables', '-A', 'FORWARD', '-d', '%s' % ipv4,
'-j', 'DROP'])
self._checkAddFirewallRules(command_list, check_command_list)
def _dropIPv4FirewallRules(self, ipv4, ipv4_list):
"""
"""
command_list = []
check_command_list = []
for ip in ipv4_list:
# Configure INPUT rules
check_command_list.append(['iptables', '-C', 'INPUT', '-s', '%s' % ip,
'-d', '%s' % ipv4, '-j', 'ACCEPT'])
command_list.append(['iptables', '-D', 'INPUT', '-s', '%s' % ip, '-d',
'%s' % ipv4, '-j', 'ACCEPT'])
# Configure OUTPUT rules
check_command_list.append(['iptables', '-C', 'OUTPUT', '-s', '%s' % ipv4,
'-d', '%s' % ip, '-j', 'ACCEPT'])
command_list.append(['iptables', '-D', 'OUTPUT', '-s', '%s' % ipv4, '-d',
'%s' % ip, '-j', 'ACCEPT'])
# Configure FORWARD rules # Configure FORWARD rules
check_command_list.append(['iptables', '-C', 'FORWARD', '-s', '%s' % ip, cmd_list.append('%s FORWARD 0 -s %s -d %s -m state --state ESTABLISHED,RELATED -j ACCEPT' % (
'-d', '%s' % ipv4, '-j', 'ACCEPT']) command, other_ip, ip))
command_list.append(['iptables', '-D', 'FORWARD', '-s', '%s' % ip, '-d',
'%s' % ipv4, '-j', 'ACCEPT']) # Drop all other requests
cmd_list.append('%s INPUT 1000 -d %s -j DROP' % (command, ip))
# Cancel all other INPUT requests cmd_list.append('%s FORWARD 1000 -d %s -j DROP' % (command, ip))
check_command_list.append(['iptables', '-C', 'INPUT', '-d', '%s' % ipv4, cmd_list.append('%s INPUT 900 -d %s -m state --state ESTABLISHED,RELATED -j DROP' % (
'-j', 'DROP']) command, ip))
command_list.append(['iptables', '-D', 'INPUT', '-d', '%s' % ipv4, cmd_list.append('%s FORWARD 900 -d %s -m state --state ESTABLISHED,RELATED -j DROP' % (
'-j', 'DROP']) command, ip))
# Cancel all other OUTPUT requests
check_command_list.append(['iptables', '-C', 'OUTPUT', '-s', '%s' % ipv4, self._checkAddFirewallRules(partition_id, cmd_list, add=add_rules)
'-j', 'DROP'])
command_list.append(['iptables', '-D', 'OUTPUT', '-s', '%s' % ipv4, def _setupComputerPartitionFirewall(self, computer_partition, ip_list, authorized_ip_list, drop_entries=False):
'-j', 'DROP'])
# Cancel all other FORWARD requests
check_command_list.append(['iptables', '-C', 'FORWARD', '-d', '%s' % ipv4,
'-j', 'DROP'])
command_list.append(['iptables', '-D', 'FORWARD', '-d', '%s' % ipv4,
'-j', 'DROP'])
self._checkAddFirewallRules(command_list, check_command_list, add=False)
def _setupComputerPartitionFirewall(self, ip_list, hosting_ip_list, drop_entries=False):
""" """
Using linux iptables, limit access to IP of this partition to all Using linux iptables, limit access to IP of this partition to all
others partitions of the same Hosting Subscription others partitions of the same Hosting Subscription
""" """
ipv4_list = [] ipv4_list = []
ipv6_list = [] ipv6_list = []
hosting_ipv4_list = [] authorized_ipv4_list = []
hosting_ipv6_list = [] authorized_ipv6_list = []
for iface, ip in ip_list:
for net_ip in ip_list:
iface, ip = (net_ip[0], net_ip[1])
if not iface.startswith('route_'):
continue
if valid_ipv4(ip): if valid_ipv4(ip):
ipv4_list.append(ip) ipv4_list.append(ip)
elif valid_ipv6(ip): elif valid_ipv6(ip):
ipv6_list.append(ip) ipv6_list.append(ip)
for iface, ip in hosting_ip_list: for iface, ip in authorized_ip_list:
if valid_ipv4(ip): if valid_ipv4(ip):
if not ip in ipv4_list: if not ip in ipv4_list:
hosting_ipv4_list.append(ip) authorized_ipv4_list.append(ip)
elif valid_ipv6(ip): elif valid_ipv6(ip):
if not ip in ipv6_list: if not ip in ipv6_list:
hosting_ipv6_list.append(ip) authorized_ipv6_list.append(ip)
filter_dict = getattr(computer_partition, '_filter_dict', None)
extra_list = filter_dict.get('authorized_sources', '').split(' ')
extra_list.extend(self.firewall_conf.get('authorized_sources', []))
for ip in extra_list:
if not ip:
continue
the_ip = ip.split('/')[0]
if valid_ipv4(the_ip):
authorized_ipv4_list.append(ip)
elif valid_ipv6(the_ip):
authorized_ipv6_list.append(ip)
if not drop_entries: if not drop_entries:
# Add or updare firewall configuration
self.logger.info("Configuring firewall...") self.logger.info("Configuring firewall...")
for ip in ipv4_list: add_rules = True
self._addIPv4FirewallRules(ip, hosting_ipv4_list)
else: else:
# Remove firewall configuration add_rules = False
self.logger.info("Removing firewall configuration...") self.logger.info("Removing firewall configuration...")
for ip in ipv4_list:
self._dropIPv4FirewallRules(ip, hosting_ipv4_list) for ip in ipv4_list:
self._addFirewallRules(computer_partition.getId(),
ip,
authorized_ipv4_list,
ip_type='ipv4',
add_rules=add_rules)
def processComputerPartition(self, computer_partition): def processComputerPartition(self, computer_partition):
""" """
...@@ -796,17 +877,21 @@ class Slapgrid(object): ...@@ -796,17 +877,21 @@ class Slapgrid(object):
# XXX this line breaks 37 tests # XXX this line breaks 37 tests
# self.logger.info(' Instance type: %s' % computer_partition.getType()) # self.logger.info(' Instance type: %s' % computer_partition.getType())
self.logger.info(' Instance status: %s' % computer_partition_state) self.logger.info(' Instance status: %s' % computer_partition_state)
partition_ip_list = parameter_dict['ip_list'] + parameter_dict.get( partition_ip_list = full_hosting_ip_list = []
if self.firewall_conf:
partition_ip_list = parameter_dict['ip_list'] + parameter_dict.get(
'full_ip_list', []) 'full_ip_list', [])
full_hosting_ip_list = computer_partition.getFullHostingIpAddressList() full_hosting_ip_list = computer_partition.getFullHostingIpAddressList()
if computer_partition_state == COMPUTER_PARTITION_STARTED_STATE: if computer_partition_state == COMPUTER_PARTITION_STARTED_STATE:
local_partition.install() local_partition.install()
computer_partition.available() computer_partition.available()
local_partition.start() local_partition.start()
self._setupComputerPartitionFirewall(partition_ip_list, if self.firewall_conf:
full_hosting_ip_list) self._setupComputerPartitionFirewall(computer_partition,
partition_ip_list,
full_hosting_ip_list)
self._checkPromises(computer_partition) self._checkPromises(computer_partition)
computer_partition.started() computer_partition.started()
elif computer_partition_state == COMPUTER_PARTITION_STOPPED_STATE: elif computer_partition_state == COMPUTER_PARTITION_STOPPED_STATE:
...@@ -815,16 +900,21 @@ class Slapgrid(object): ...@@ -815,16 +900,21 @@ class Slapgrid(object):
# propagate the state to children if any. # propagate the state to children if any.
local_partition.install() local_partition.install()
computer_partition.available() computer_partition.available()
self._setupComputerPartitionFirewall(partition_ip_list, if self.firewall_conf:
full_hosting_ip_list) self._setupComputerPartitionFirewall(computer_partition,
partition_ip_list,
full_hosting_ip_list)
finally: finally:
# Instance has to be stopped even if buildout/reporting is wrong. # Instance has to be stopped even if buildout/reporting is wrong.
local_partition.stop() local_partition.stop()
computer_partition.stopped() computer_partition.stopped()
elif computer_partition_state == COMPUTER_PARTITION_DESTROYED_STATE: elif computer_partition_state == COMPUTER_PARTITION_DESTROYED_STATE:
local_partition.stop() local_partition.stop()
self._setupComputerPartitionFirewall(partition_ip_list, if self.firewall_conf:
full_hosting_ip_list, drop_entries=True) self._setupComputerPartitionFirewall(computer_partition,
partition_ip_list,
full_hosting_ip_list,
drop_entries=True)
try: try:
computer_partition.stopped() computer_partition.stopped()
except (SystemExit, KeyboardInterrupt): except (SystemExit, KeyboardInterrupt):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment