diff options
Diffstat (limited to 'ipalib')
-rw-r--r-- | ipalib/constants.py | 1 | ||||
-rw-r--r-- | ipalib/errors.py | 16 | ||||
-rw-r--r-- | ipalib/plugins/config.py | 41 | ||||
-rw-r--r-- | ipalib/plugins/hbacrule.py | 8 | ||||
-rw-r--r-- | ipalib/plugins/selinuxusermap.py | 441 |
5 files changed, 506 insertions, 1 deletions
diff --git a/ipalib/constants.py b/ipalib/constants.py index ba5f470b0..96cf3ba60 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -96,6 +96,7 @@ DEFAULT_CONFIG = ( ('container_sudocmdgroup', 'cn=sudocmdgroups,cn=sudo'), ('container_entitlements', 'cn=entitlements,cn=etc'), ('container_automember', 'cn=automember,cn=etc'), + ('container_selinux', 'cn=usermap,cn=selinux'), # Ports, hosts, and URIs: # FIXME: let's renamed xmlrpc_uri to rpc_xml_uri diff --git a/ipalib/errors.py b/ipalib/errors.py index 3434c26be..4463fee70 100644 --- a/ipalib/errors.py +++ b/ipalib/errors.py @@ -1512,6 +1512,22 @@ class NotRegisteredError(ExecutionError): format = _('Not registered yet') +class DependentEntry(ExecutionError): + """ + **4307** Raised when an entry being deleted has dependencies + + For example: + >>> raise DependentEntry(label=u'SELinux User Map', key=u'test', dependent=u'test1') + Traceback (most recent call last): + ... + DependentEntry: Not registered yet + + """ + + errno = 4307 + format = _('%(key)s cannot be deleted because %(label)s %(dependent)s requires it') + + ############################################################################## # 5000 - 5999: Generic errors diff --git a/ipalib/plugins/config.py b/ipalib/plugins/config.py index 20c4eda72..0c238ac98 100644 --- a/ipalib/plugins/config.py +++ b/ipalib/plugins/config.py @@ -47,6 +47,9 @@ Certificate Subject base: the configured certificate subject base, Password plug-in features: currently defines additional hashes that the password will generate (there may be other conditions). +When setting the order list for mapping SELinux users you may need to +quote the value so it isn't interpreted by the shell. + EXAMPLES: Show basic server configuration: @@ -66,6 +69,9 @@ EXAMPLES: Enable migration mode to make "ipa migrate-ds" command operational: ipa config-mod --enable-migration=TRUE + + Define SELinux user map order: + ipa config-mod --ipaselinuxusermaporder='guest_u:s0$xguest_u:s0$user_u:s0-s0:c0.c1023$staff_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023' """) def validate_searchtimelimit(ugettext, limit): @@ -83,7 +89,7 @@ class config(LDAPObject): 'ipadefaultprimarygroup', 'ipadefaultemaildomain', 'ipasearchtimelimit', 'ipasearchrecordslimit', 'ipausersearchfields', 'ipagroupsearchfields', 'ipamigrationenabled', 'ipacertificatesubjectbase', - 'ipapwdexpadvnotify', + 'ipapwdexpadvnotify', 'ipaselinuxusermaporder', 'ipaselinuxusermapdefault', ] label = _('Configuration') @@ -172,6 +178,14 @@ class config(LDAPObject): doc=_('Extra hashes to generate in password plug-in'), flags=['no_update'], ), + Str('ipaselinuxusermaporder?', + label=_('SELinux user map order'), + doc=_('Order in increasing priority of SELinux users, delimited by $'), + ), + Str('ipaselinuxusermapdefault?', + label=_('Default SELinux user'), + doc=_('Default SELinux user when no match is found in SELinux map rule'), + ), ) def get_dn(self, *keys, **kwargs): @@ -228,6 +242,31 @@ class config_mod(LDAPUpdate): error=_('%s default attribute %s would not be allowed!') \ % (obj, obj_attr)) + if 'ipaselinuxusermapdefault' in options and options['ipaselinuxusermapdefault'] is None: + raise errors.ValidationError(name='ipaselinuxusermapdefault', + error=_('SELinux user map default user may not be empty')) + + # Make sure the default user is in the list + if 'ipaselinuxusermapdefault' in options or \ + 'ipaselinuxusermaporder' in options: + config = None + if 'ipaselinuxusermapdefault' in options: + defaultuser = options['ipaselinuxusermapdefault'] + else: + config = ldap.get_ipa_config()[1] + defaultuser = config['ipaselinuxusermapdefault'] + + if 'ipaselinuxusermaporder' in options: + order = options['ipaselinuxusermaporder'] + else: + if not config: + config = ldap.get_ipa_config()[1] + order = config['ipaselinuxusermaporder'] + userlist = order[0].split('$') + if defaultuser not in userlist: + raise errors.ValidationError(name='ipaselinuxusermaporder', + error=_('Default SELinux user map default user not in order list')) + return dn api.register(config_mod) diff --git a/ipalib/plugins/hbacrule.py b/ipalib/plugins/hbacrule.py index f9f04529f..92b656d66 100644 --- a/ipalib/plugins/hbacrule.py +++ b/ipalib/plugins/hbacrule.py @@ -239,6 +239,14 @@ class hbacrule_del(LDAPDelete): msg_summary = _('Deleted HBAC rule "%(value)s"') + def pre_callback(self, ldap, dn, *keys, **options): + kw = dict(seealso=dn) + _entries = api.Command.selinuxusermap_find(None, **kw) + if _entries['count']: + raise errors.DependentEntry(key=keys[0], label=self.api.Object['selinuxusermap'].label_singular, dependent=_entries['result'][0]['cn'][0]) + + return dn + api.register(hbacrule_del) diff --git a/ipalib/plugins/selinuxusermap.py b/ipalib/plugins/selinuxusermap.py new file mode 100644 index 000000000..475376f6e --- /dev/null +++ b/ipalib/plugins/selinuxusermap.py @@ -0,0 +1,441 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2011 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from ipalib import api, errors +from ipalib import Str, StrEnum +from ipalib.plugins.baseldap import * +from ipalib import _, ngettext +from ipalib.plugins.hbacrule import is_all + +__doc__ = _(""" +SELinux User Mapping + +Map IPA users to SELinux users by host. + +Hosts, hostgroups, users and groups can be either defined within +the rule or it may point to an existing HBAC rule. + +EXAMPLES: + + Create a rule, "test1", that sets all users to xguest_u:s0 on the host "server": + ipa selinuxusermap-add --usercat=all --selinuxuser=xguest_u:s0 test1 + ipa selinuxusermap-add-host --hosts=server.example.com test1 + + Create a rule, "test2", that sets all users to guest_u:s0 and uses an existing HBAC rule for users and hosts: + ipa selinuxusermap-add --usercat=all --hbacrule=webserver --selinuxuser=guest_u:s0 test1 + + Display the properties of a named HBAC rule: + ipa selinuxusermap-show test1 + + Create a rule for a specific user. This sets the SELinux context for + user john to unconfined_u:s0-s0:c0.c1023 on any machine: + ipa selinuxusermap-add --hostcat=all --selinuxuser=unconfined_u:s0-s0:c0.c1023 john_unconfined + ipa selinuxusermap-add-user --users=john john_unconfined + + Disable a named rule: + ipa selinuxusermap-disable test1 + + Enable a named rule: + ipa selinuxusermap-enable test1 + + Remove a named rule: + ipa selinuxusermap-del john_unconfined + +SEEALSO: + + The list controlling the order in which the SELinux user map is applied + and the default SELinux user are available in the config-show commond. +""") + +notboth_err = _('HBAC rule and local members cannot both be set') + +def validate_selinuxuser(ugettext, user): + """ + An SELinux user has 3 components: user:MLS:MCS + user traditionally ends with _u but this is not mandatory. Regex is ^[a-zA-Z][a-zA-Z_]* + The MLS part can only be + Level: s[0-15](-s[0-15]) + Then MCS could be c[0-1023].c[0-1023] and/or c[0-1023]-c[0-c0123] + Meaning + s0 s0-s1 s0-s15:c0.c1023 s0-s1:c0,c2,c15.c26 s0-s0:c0.c1023 + + Returns a message on invalid, returns nothing on valid. + """ + regex_name = re.compile(r'^[a-zA-Z][a-zA-Z_]*$') + regex_mls = re.compile(r'^s[0-9][1-5]{0,1}(-s[0-9][1-5]{0,1}){0,1}$') + regex_mcs = re.compile(r'^c(\d+)([.,-]c(\d+))*?$') + + # If we add in ::: we don't have to check to see if some values are + # empty + (name, mls, mcs, ignore) = (user + ':::').split(':',3) + + if not regex_name.match(name): + return _('Invalid SELinux user name, only a-Z and _ are allowed') + if mls and not regex_mls.match(mls): + return _('Invalid MLS value, must match s[0-15](-s[0-15])') + if mcs and not regex_mcs.match(mcs): + return _('Invalid MCS value, must match c[0-1023].c[0-1023] and/or c[0-1023]-c[0-c0123]') + + return None + +def validate_selinuxuser_inlist(ldap, user): + """ + Ensure the user is in the list of allowed SELinux users. + + Returns nothing if the user is found, raises an exception otherwise. + """ + config = ldap.get_ipa_config()[1] + item = config.get('ipaselinuxusermaporder', []) + if len(item) != 1: + raise errors.NotFound(reason=_('SELinux user map list not found in configuration')) + userlist = item[0].split('$') + if user not in userlist: + raise errors.NotFound(reason=_('SELinux user %(user)s not found in ordering list (in config)') % dict(user=user)) + + return + +class selinuxusermap(LDAPObject): + """ + SELinux User Map object. + """ + container_dn = api.env.container_selinux + object_name = _('SELinux User Map rule') + object_name_plural = _('SELinux User Map rules') + object_class = ['ipaassociation', 'ipaselinuxusermap'] + default_attributes = [ + 'cn', 'ipaenabledflag', + 'description', 'usercategory', 'hostcategory', + 'ipaenabledflag', 'memberuser', 'memberhost', + 'memberhostgroup', 'seealso', 'ipaselinuxuser', + ] + uuid_attribute = 'ipauniqueid' + rdn_attribute = 'ipauniqueid' + attribute_members = { + 'memberuser': ['user', 'group'], + 'memberhost': ['host', 'hostgroup'], + } + + # These maps will not show as members of other entries + + label = _('SELinux User Maps') + label_singular = _('SELinux User Map') + + takes_params = ( + Str('cn', + cli_name='name', + label=_('Rule name'), + primary_key=True, + ), + Str('ipaselinuxuser', validate_selinuxuser, + cli_name='selinuxuser', + label=_('SELinux User'), + ), + Str('seealso?', + cli_name='hbacrule', + label=_('HBAC Rule'), + doc=_('HBAC Rule that defines the users, groups and hostgroups'), + ), + StrEnum('usercategory?', + cli_name='usercat', + label=_('User category'), + doc=_('User category the rule applies to'), + values=(u'all', ), + ), + StrEnum('hostcategory?', + cli_name='hostcat', + label=_('Host category'), + doc=_('Host category the rule applies to'), + values=(u'all', ), + ), + Str('description?', + cli_name='desc', + label=_('Description'), + ), + Flag('ipaenabledflag?', + label=_('Enabled'), + flags=['no_create', 'no_update', 'no_search'], + ), + Str('memberuser_user?', + label=_('Users'), + flags=['no_create', 'no_update', 'no_search'], + ), + Str('memberuser_group?', + label=_('User Groups'), + flags=['no_create', 'no_update', 'no_search'], + ), + Str('memberhost_host?', + label=_('Hosts'), + flags=['no_create', 'no_update', 'no_search'], + ), + Str('memberhost_hostgroup?', + label=_('Host Groups'), + flags=['no_create', 'no_update', 'no_search'], + ), + ) + + def _normalize_seealso(self, seealso): + """ + Given a HBAC rule name verify its existence and return the dn. + """ + if not seealso: + return None + + try: + dn = DN(seealso) + return str(dn) + except ValueError: + try: + (dn, entry_attrs) = self.backend.find_entry_by_attr( + self.api.Object['hbacrule'].primary_key.name, seealso, self.api.Object['hbacrule'].object_class, [''], self.api.Object['hbacrule'].container_dn) + seealso = dn + except errors.NotFound: + raise errors.NotFound(reason=_('HBAC rule %(rule)s not found') % dict(rule=seealso)) + + return seealso + + def _convert_seealso(self, ldap, entry_attrs, **options): + """ + Convert an HBAC rule dn into a name + """ + if options.get('raw', False): + return + + if 'seealso' in entry_attrs: + (hbac_dn, hbac_attrs) = ldap.get_entry(entry_attrs['seealso'][0], ['cn']) + entry_attrs['seealso'] = hbac_attrs['cn'][0] + +api.register(selinuxusermap) + + +class selinuxusermap_add(LDAPCreate): + __doc__ = _('Create a new SELinux User Map.') + + msg_summary = _('Added SELinux User Map "%(value)s"') + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + # rules are enabled by default + entry_attrs['ipaenabledflag'] = 'TRUE' + validate_selinuxuser_inlist(ldap, entry_attrs['ipaselinuxuser']) + if 'seealso' in options: + entry_attrs['seealso'] = self.obj._normalize_seealso(options['seealso']) + + return dn + + def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + self.obj._convert_seealso(ldap, entry_attrs, **options) + + return dn + +api.register(selinuxusermap_add) + + +class selinuxusermap_del(LDAPDelete): + __doc__ = _('Delete a SELinux User Map.') + + msg_summary = _('Deleted SELinux User Map "%(value)s"') + +api.register(selinuxusermap_del) + + +class selinuxusermap_mod(LDAPUpdate): + __doc__ = _('Modify a SELinux User Map.') + + msg_summary = _('Modified SELinux User Map "%(value)s"') + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + try: + (_dn, _entry_attrs) = ldap.get_entry(dn, attrs_list) + except errors.NotFound: + self.obj.handle_not_found(*keys) + + if 'seealso' in options and ('usercategory' in _entry_attrs or + 'hostcategory' in _entry_attrs or + 'memberuser' in _entry_attrs or + 'memberhost' in _entry_attrs): + raise errors.MutuallyExclusiveError(reason=notboth_err) + + if is_all(options, 'usercategory') and 'memberuser' in entry_attrs: + raise errors.MutuallyExclusiveError(reason="user category cannot be set to 'all' while there are allowed users") + if is_all(options, 'hostcategory') and 'memberhost' in entry_attrs: + raise errors.MutuallyExclusiveError(reason="host category cannot be set to 'all' while there are allowed hosts") + + if 'ipaselinuxuser' in options: + validate_selinuxuser_inlist(ldap, options['ipaselinuxuser']) + + if 'seealso' in options: + entry_attrs['seealso'] = self.obj._normalize_seealso(options['seealso']) + return dn + + def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + self.obj._convert_seealso(ldap, entry_attrs, **options) + return dn + +api.register(selinuxusermap_mod) + + +class selinuxusermap_find(LDAPSearch): + __doc__ = _('Search for SELinux User Maps.') + + msg_summary = ngettext( + '%(count)d SELinux User Map matched', '%(count)d SELinux User Maps matched', 0 + ) + + def execute(self, *args, **options): + # If searching on hbacrule we need to find the uuid to search on + if 'seealso' in options: + kw = dict(cn=options['seealso'], all=True) + _entries = api.Command.hbacrule_find(None, **kw)['result'] + del options['seealso'] + if _entries: + options['seealso'] = _entries[0]['dn'] + + return super(selinuxusermap_find, self).execute(*args, **options) + + def post_callback(self, ldap, entries, truncated, *args, **options): + if options.get('pkey_only', False): + return + for entry in entries: + (dn, attrs) = entry + self.obj._convert_seealso(ldap, attrs, **options) + +api.register(selinuxusermap_find) + + +class selinuxusermap_show(LDAPRetrieve): + __doc__ = _('Display the properties of a SELinux User Map rule.') + + def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + self.obj._convert_seealso(ldap, entry_attrs, **options) + return dn + +api.register(selinuxusermap_show) + + +class selinuxusermap_enable(LDAPQuery): + __doc__ = _('Enable an SELinux User Map rule.') + + msg_summary = _('Enabled SELinux User Map "%(value)s"') + has_output = output.standard_value + + def execute(self, cn): + ldap = self.obj.backend + + dn = self.obj.get_dn(cn) + entry_attrs = {'ipaenabledflag': 'TRUE'} + + try: + ldap.update_entry(dn, entry_attrs) + except errors.EmptyModlist: + raise errors.AlreadyActive() + except errors.NotFound: + self.obj.handle_not_found(cn) + + return dict( + result=True, + value=cn, + ) + +api.register(selinuxusermap_enable) + + +class selinuxusermap_disable(LDAPQuery): + __doc__ = _('Disable an SELinux User Map rule.') + + msg_summary = _('Disabled SELinux User Map "%(value)s"') + has_output = output.standard_value + + def execute(self, cn): + ldap = self.obj.backend + + dn = self.obj.get_dn(cn) + entry_attrs = {'ipaenabledflag': 'FALSE'} + + try: + ldap.update_entry(dn, entry_attrs) + except errors.EmptyModlist: + raise errors.AlreadyInactive() + except errors.NotFound: + self.obj.handle_not_found(cn) + + return dict( + result=True, + value=cn, + ) + +api.register(selinuxusermap_disable) + + +class selinuxusermap_add_user(LDAPAddMember): + __doc__ = _('Add users and groups to an SELinux User Map rule.') + + member_attributes = ['memberuser'] + member_count_out = ('%i object added.', '%i objects added.') + + def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + try: + (dn, entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes) + except errors.NotFound: + self.obj.handle_not_found(*keys) + if 'usercategory' in entry_attrs and \ + entry_attrs['usercategory'][0].lower() == 'all': + raise errors.MutuallyExclusiveError(reason="users cannot be added when user category='all'") + if 'seealso' in entry_attrs: + raise errors.MutuallyExclusiveError(reason=notboth_err) + return dn + +api.register(selinuxusermap_add_user) + + +class selinuxusermap_remove_user(LDAPRemoveMember): + __doc__ = _('Remove users and groups from an SELinux User Map rule.') + + member_attributes = ['memberuser'] + member_count_out = ('%i object removed.', '%i objects removed.') + +api.register(selinuxusermap_remove_user) + + +class selinuxusermap_add_host(LDAPAddMember): + __doc__ = _('Add target hosts and hostgroups to an SELinux User Map rule.') + + member_attributes = ['memberhost'] + member_count_out = ('%i object added.', '%i objects added.') + + def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + try: + (dn, entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes) + except errors.NotFound: + self.obj.handle_not_found(*keys) + if 'hostcategory' in entry_attrs and \ + entry_attrs['hostcategory'][0].lower() == 'all': + raise errors.MutuallyExclusiveError(reason="hosts cannot be added when host category='all'") + if 'seealso' in entry_attrs: + raise errors.MutuallyExclusiveError(reason=notboth_err) + return dn + +api.register(selinuxusermap_add_host) + + +class selinuxusermap_remove_host(LDAPRemoveMember): + __doc__ = _('Remove target hosts and hostgroups from an SELinux User Map rule.') + + member_attributes = ['memberhost'] + member_count_out = ('%i object removed.', '%i objects removed.') + +api.register(selinuxusermap_remove_host) |