diff --git a/actions/firewall b/actions/firewall new file mode 100755 index 000000000..88a921d2b --- /dev/null +++ b/actions/firewall @@ -0,0 +1,96 @@ +#!/usr/bin/python +# -*- mode: python -*- +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +""" +Configuration helper for Plinth firewall inteface +""" + +import argparse +import os +import re +import subprocess + + +def parse_arguments(): + """Return parsed command line arguments as dictionary""" + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') + + # Get installed status + subparsers.add_parser('get-installed', + help='Get whether firewalld is installed') + + # Get status + subparsers.add_parser('get-status', help='Get whether firewalld is running') + + # Get service status + subparsers.add_parser('get-enabled-services', help='Get list of enabled services') + + # Add a service + add_service = subparsers.add_parser('add-service', help='Add a service') + add_service.add_argument('service', help='Name of the service to add') + + # Remove a service status + remove_service = subparsers.add_parser('remove-service', help='Remove a service') + remove_service.add_argument('service', help='Name of the service to remove') + + return parser.parse_args() + + +def subcommand_get_installed(_): + """Print whether firewalld is installed""" + with open('/dev/null', 'w') as file_handle: + status = subprocess.call(['which', 'firewalld'], stdout=file_handle) + + print 'installed' if not status else 'not installed' + + +def subcommand_get_status(_): + """Print status of the firewalld service""" + subprocess.call(['firewall-cmd', '--state']) + + +def subcommand_get_enabled_services(_): + """Print the status of variours services""" + subprocess.call(['firewall-cmd', '--list-services']) + + +def subcommand_add_service(arguments): + """Permit a service in the firewall""" + subprocess.call(['firewall-cmd', '--add-service', arguments.service]) + subprocess.call(['firewall-cmd', '--permanent', '--add-service', arguments.service]) + + +def subcommand_remove_service(arguments): + """Block a service in the firewall""" + subprocess.call(['firewall-cmd', '--remove-service', arguments.service]) + subprocess.call(['firewall-cmd', '--permanent', '--remove-service', arguments.service]) + + +def main(): + """Parse arguments and perform all duties""" + arguments = parse_arguments() + + subcommand = arguments.subcommand.replace('-', '_') + subcommand_method = globals()['subcommand_' + subcommand] + subcommand_method(arguments) + + +if __name__ == "__main__": + main() diff --git a/modules/firewall.py b/modules/firewall.py new file mode 120000 index 000000000..1d198303f --- /dev/null +++ b/modules/firewall.py @@ -0,0 +1 @@ +installed/system/firewall.py \ No newline at end of file diff --git a/modules/installed/system/firewall.py b/modules/installed/system/firewall.py new file mode 100644 index 000000000..f3faf78ea --- /dev/null +++ b/modules/installed/system/firewall.py @@ -0,0 +1,191 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +""" +Plinth module to configure a firewall +""" + +import cherrypy +from gettext import gettext as _ + +import actions +import cfg +from modules.auth import require +from plugin_mount import PagePlugin +import service as service_module + + +class Firewall(PagePlugin): + """Firewall menu entry and introduction page""" + order = 40 + + def __init__(self, *args, **kwargs): + PagePlugin.__init__(self, *args, **kwargs) + + self.register_page('sys.firewall') + cfg.html_root.sys.menu.add_item(_('Firewall'), 'icon-flag', + '/sys/firewall', 50) + + service_module.ENABLED.connect(self.on_service_enabled) + + @cherrypy.expose + @require() + def index(self, **kwargs): + """Serve introcution page""" + del kwargs # Unused + + # XXX: Use templates here instead of generating HTML + main = _(''' +

Firewall is a network security system that controls the incoming +and outgoing network traffic on your {box_name}. Keeping a firewall +enabled and properly configured reduces risk of security threat from +the Internet.

+ +

The following the current status:

+''').format(box_name=cfg.box_name) + + if not self.get_installed_status(): + status = _(''' +

Firewall is not installed. Please install it. Firewall comes +pre-installed with {box_name}. On any Debian based system (such as +{box_name}) you may install it using the command 'aptitude install +firewalld'

''').format(box_name=cfg.box_name) + + return self.fill_template(title=_("Firewall"), main=main + status) + + if not self.get_enabled_status(): + status = _(''' +

Firewall daemon is not running. Please run it. Firewall comes +enabled by default on {box_name}. On any Debian based system (such as +{box_name}) you may run it using the command 'service firewalld start' +or in case of system with systemd 'systemctl start firewalld'

+''').format(box_name=cfg.box_name) + + return self.fill_template(title=_("Firewall"), main=main + status) + + enabled_services = self.get_enabled_services() + services_info = '' + + footnote = ''' +

The operation of the firewall is automatic. When you enable a +service it is automatically permitted in the firewall and you disable +a service is automatically disabled in the firewall.

''' + + return self.fill_template(title=_("Firewall"), main=main + + services_info + footnote) + + def get_installed_status(self): + """Return whether firewall is installed""" + output = self._run(['get-installed']) + return output.split()[0] == 'installed' + + def get_enabled_status(self): + """Return whether firewall is installed""" + output = self._run(['get-status']) + return output.split()[0] == 'running' + + def get_enabled_services(self): + """Return the status of various services currently enabled""" + output = self._run(['get-enabled-services']) + return output.split() + + def add_service(self, port): + """Enable a service in firewall""" + self._run(['add-service', port]) + + def remove_service(self, port): + """Remove a service in firewall""" + self._run(['remove-service', port]) + + def on_service_enabled(self, sender, service_id, enabled, **kwargs): + """ + Enable/disable firewall ports when a service is + enabled/disabled. + """ + del sender # Unused + del kwargs # Unused + + enabled_services = self.get_enabled_services() + + cfg.log.info('Service enabled - %s, %s' % (service_id, enabled)) + for port in service_module.SERVICES[service_id].ports: + if enabled: + if port not in enabled_services: + self.add_service(port) + else: + if port in enabled_services: + enabled_services_on_port = [ + service_.is_enabled() + for service_ in service_module.SERVICES.values() + if port in service_.ports and + service_id != service_.service_id] + if not any(enabled_services_on_port): + self.remove_service(port) + + @staticmethod + def _run(arguments, superuser=True): + """Run an given command and raise exception if there was an error""" + command = 'firewall' + + cfg.log.info('Running command - %s, %s, %s' % (command, arguments, + superuser)) + + if superuser: + output, error = actions.superuser_run(command, arguments) + else: + output, error = actions.run(command, arguments) + + if error: + raise Exception('Error setting/getting firewalld confguration - %s' + % error) + + return output diff --git a/themes/default/css/bootstrap.css b/themes/default/css/bootstrap.css index c3e0c0053..e1d175d79 100644 --- a/themes/default/css/bootstrap.css +++ b/themes/default/css/bootstrap.css @@ -3494,3 +3494,11 @@ a.thumbnail:hover { .invisible { visibility: hidden; } + +.firewall-permitted { + color: green; +} + +.firewall-blocked { + color: red; +}