--- a/WebappConfig/db.py +++ b/WebappConfig/db.py @@ -537,8 +535,8 @@ ''' import WebappConfig.filetype - server_files = [] - server_dirs = [] + self.server_files = [] + self.server_dirs = [] config_files = [] if os.access(self.appdir() + '/' + config_owned, os.R_OK): @@ -549,7 +549,7 @@ class WebappSource(AppHierarchy): if os.access(self.appdir() + '/' + server_owned, os.R_OK): flist = open(self.appdir() + '/' + server_owned) - server_files = flist.readlines() + self.server_files = flist.readlines() OUT.debug('Identified server-owned files.', 7) @@ -557,15 +557,15 @@ class WebappSource(AppHierarchy): if os.access(self.appdir() + '/' + server_owned_r, os.R_OK): flist = open(self.appdir() + '/' + server_owned_r) - server_dirs = flist.readlines() + self.server_dirs = flist.readlines() OUT.debug('Identified server-owned directories.', 7) flist.close() self.__types = WebappConfig.filetype.FileType(config_files, - server_files, - server_dirs, + self.server_files, + self.server_dirs, virtual_files, default_dirs) --- a/WebappConfig/config.py +++ b/WebappConfig/config.py @@ -293,6 +293,7 @@ class Config: 'wa_installs' : '%(my_persistdir)s/%(wa_installsbase)s', 'wa_postinstallinfo': '%(my_appdir)s/post-install-instructions.txt', + 'g_selinux' : 'no', } # Setup basic defaults @@ -541,6 +542,15 @@ class Config: 'rt all values of DEFAULT_DIRS and will report a' 'n error') + group.add_option('--selinux-module', + type = 'choice', + choices = ['yes', + 'no'], + help = 'If activated, webapp-config will use a S' + 'ELinux module to set the file context of server' + ' owned files to \'httpd_sys_rw_content_t\'. Def' + 'ault is' + self.config.get('USER', 'g_selinux')) + self.parser.add_option_group(group) #----------------------------------------------------------------- @@ -874,7 +884,9 @@ class Config: 'default_dirs' : 'vhost_config_default_dirs', 'pretend' : 'g_pretend', 'verbose' : 'g_verbose', - 'bug_report' : 'g_bugreport'} + 'bug_report' : 'g_bugreport', + 'selinux' : 'g_selinux', + } for i in option_to_config.keys(): if i in options.__dict__ and options.__dict__[i]: @@ -1496,7 +1508,9 @@ class Config: 'orig' : self.maybe_get('g_orig_installdir'), 'upgrade' : self.upgrading(), 'verbose' : self.verbose(), - 'pretend' : self.pretend()} + 'pretend' : self.pretend(), + 'selinux' : self.maybe_get('g_selinux') == 'yes', + } return allowed_servers[server](directories, self.create_permissions(), --- /dev/null +++ b/WebappConfig/selinux.py @@ -0,0 +1,91 @@ +#!/usr/bin/python -O +# +# /usr/sbin/webapp-config +# Python script for managing the deployment of web-based +# applications +# +# Originally written for the Gentoo Linux distribution +# +# Copyright (c) 1999-2007 Authors +# Released under v2 of the GNU GPL +# +# ======================================================================== + +# ======================================================================== +# Dependencies +# ------------------------------------------------------------------------ + +import os, os.path, re, shutil, subprocess, tempfile + +from WebappConfig.debug import OUT + +# ======================================================================== +# Constants +# ------------------------------------------------------------------------ + +MAKE_CONF_FILE = ['/etc/make.conf', '/etc/portage/make.conf'] + +# ======================================================================== +# SELinux handler +# ------------------------------------------------------------------------ + +class SELinux: + + def __init__(self, package_name, vhost_hostname, policy_types = ()): + self.package_name = package_name + self.vhost_hostname = vhost_hostname + self.policy_name = '{}_{}'.format(package_name, vhost_hostname) + self.policy_types = policy_types + if self.policy_types is (): + for filename in MAKE_CONF_FILE: + try: + with open(filename) as file: + for line in file.readlines(): + if line.startswith('POLICY_TYPES='): + self.policy_types = line[len('POLICY_TYPES='):-1].strip(' "').split() + break + if self.policy_types is not None: + break + except IOError: + pass + if self.policy_types is (): + OUT.die('No SELinux policy was found, abording') + + def remove_module(self): + OUT.info('Removing SELinux modules') + for policy in self.policy_types: + if subprocess.call(['semodule', '-s', policy, '-r', self.policy_name]): + OUT.warn('Unable to remove {} SELinux module for {} @ {}'.format(policy, self.package_name, self.vhost_hostname)) + + def create_module(self, package_version, vhost_root, server_files, server_dirs): + temp_dir = tempfile.mkdtemp() + OUT.info('Creating SELinux modules') + for policy in self.policy_types: + base_dir = os.path.join(temp_dir, policy) + os.mkdir(base_dir) + with open(os.path.join(base_dir, '{}.te'.format(self.policy_name)), 'w') as te_file: + te_file.write('policy_module({},{})\n'.format(self.policy_name, package_version)) + te_file.write('require {\n') + te_file.write(' type httpd_sys_rw_content_t;\n') + te_file.write('}') + with open(os.path.join(base_dir, '{}.fc'.format(self.policy_name)), 'w') as fc_file: + for files in server_files: + fc_file.write('{} gen_context(system_u:object_r:httpd_sys_rw_content_t,s0)\n'.format(SELinux.filename_re_escape(os.path.join(vhost_root, files.rstrip('\n'))))) + for dirs in server_dirs: + fc_file.write('{}(/.*)? gen_context(system_u:object_r:httpd_sys_rw_content_t,s0)\n'.format(SELinux.filename_re_escape(os.path.join(vhost_root, dirs.rstrip('\n'))))) + if subprocess.call(['make', '-s', '-C', base_dir, '-f', os.path.join('/usr/share/selinux', policy, 'include/Makefile'), '{}.pp'.format(self.policy_name)]): + if not os.path.isfile(os.path.join('/usr/share/selinux', policy, 'include/Makefile')): + OUT.die('Policy {} is not supported, please fix your configuration'.format(policy)) + OUT.die('Unable to create {} SELinux module for {} @ {}'.format(policy, self.package_name, self.vhost_hostname)) + OUT.info('Installing SELinux modules') + try: + for policy in self.policy_types: + if subprocess.call(['semodule', '-s', policy, '-i', os.path.join(temp_dir, policy, '{}.pp'.format(self.policy_name))]): + OUT.die('Unable to install {} SELinux module for {} @ {}'.format(policy, self.package_name, self.vhost_hostname)) + except IOError: + OUT.die('"semodule" was not found, please check you SELinux installation') + shutil.rmtree(temp_dir) + + @staticmethod + def filename_re_escape(string): + return re.sub('\.', '\.', string) --- a/WebappConfig/server.py +++ b/WebappConfig/server.py @@ -24,6 +24,7 @@ import os, os.path from WebappConfig.debug import OUT from WebappConfig.worker import WebappRemove, WebappAdd from WebappConfig.permissions import get_group, get_user +from WebappConfig.selinux import SELinux from WebappConfig.wrapper import package_installed @@ -95,6 +96,11 @@ class Basic: self.__v = flags['verbose'] self.__p = flags['pretend'] + if flags['selinux']: + self.__selinux = SELinux(self.__ws.pn, flags['host']) + else: + self.__selinux = None + wd = WebappRemove(self.__content, self.__v, self.__p) @@ -176,6 +182,11 @@ class Basic: self.__db.remove(self.__destd) + # Remove the selinux module + + if self.__selinux is not None: + self.__selinux.remove_module() + # did we leave anything behind? if self.file_behind_flag: @@ -188,6 +199,13 @@ class Basic: OUT.debug('Basic server install', 7) + # Create the selinux module + + if self.__selinux is not None: + self.__selinux.create_module(self.__ws.pvr, self.__vhostroot, + self.__ws.server_files, + self.__ws.server_dirs) + # The root of the virtual install location needs to exist if not os.path.isdir(self.__destd) and not self.__p: @@ -314,6 +314,11 @@ class Basic: self.__content.write() + # Warn the user about needed relabelling + + OUT.warn('You probably need to relabel the new installation, using for' + 'example "restorecon -R ' + self.__destd + '"') + # and we're done OUT.info('Install completed - success', 1)