From a228d7a3cb32b14ff24b47adb14d896d317f6312 Mon Sep 17 00:00:00 2001 From: Tomas Babej Date: Wed, 14 May 2014 12:52:26 +0200 Subject: sudorule: Allow using hostmasks for setting allowed hosts Adds a new --hostmasks option to sudorule-add-host and sudorule-remove-host commands, which allows setting a range of hosts specified by a hostmask. https://fedorahosted.org/freeipa/ticket/4274 Reviewed-By: Petr Viktorin --- ipalib/plugins/sudorule.py | 73 ++++++++++++++++++++++++++++++++++++++++++++-- ipalib/util.py | 7 +++++ 2 files changed, 78 insertions(+), 2 deletions(-) (limited to 'ipalib') diff --git a/ipalib/plugins/sudorule.py b/ipalib/plugins/sudorule.py index 87242ead3..a304373b3 100644 --- a/ipalib/plugins/sudorule.py +++ b/ipalib/plugins/sudorule.py @@ -17,6 +17,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import netaddr + from ipalib import api, errors from ipalib import Str, StrEnum, Bool, Int from ipalib.plugable import Registry @@ -30,6 +32,7 @@ from ipalib.plugins.baseldap import (LDAPObject, LDAPCreate, LDAPDelete, external_host_param) from ipalib.plugins.hbacrule import is_all from ipalib import _, ngettext +from ipalib.util import validate_hostmask from ipapython.dn import DN __doc__ = _(""" @@ -94,6 +97,12 @@ def deprecated(attribute): error=_('this option has been deprecated.')) +hostmask_membership_param = Str('hostmask?', validate_hostmask, + label=_('host masks of allowed hosts'), + flags=['no_create', 'no_update', 'no_search'], + multivalue=True, + ) + def validate_externaluser(ugettext, value): deprecated('externaluser') @@ -123,7 +132,7 @@ class sudorule(LDAPObject): 'memberallowcmd', 'memberdenycmd', 'ipasudoopt', 'ipasudorunas', 'ipasudorunasgroup', 'ipasudorunasusercategory', 'ipasudorunasgroupcategory', - 'sudoorder', + 'sudoorder', 'hostmask', ] uuid_attribute = 'ipauniqueid' rdn_attribute = 'ipauniqueid' @@ -267,6 +276,12 @@ class sudorule(LDAPObject): label=_('Host Groups'), flags=['no_create', 'no_update', 'no_search'], ), + Str('hostmask', validate_hostmask, + normalizer=lambda x: unicode(netaddr.IPNetwork(x).cidr), + label=_('Host Masks'), + flags=['no_create', 'no_update', 'no_search'], + multivalue=True, + ), Str('memberallowcmd_sudocmd?', label=_('Sudo Allow Commands'), flags=['no_create', 'no_update', 'no_search'], @@ -577,6 +592,11 @@ class sudorule_add_host(LDAPAddMember): member_attributes = ['memberhost'] member_count_out = ('%i object added.', '%i objects added.') + def get_options(self): + for option in super(sudorule_add_host, self).get_options(): + yield option + yield hostmask_membership_param + def pre_callback(self, ldap, dn, found, not_found, *keys, **options): assert isinstance(dn, DN) try: @@ -593,7 +613,30 @@ class sudorule_add_host(LDAPAddMember): def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): assert isinstance(dn, DN) - return add_external_post_callback('memberhost', 'host', 'externalhost', ldap, completed, failed, dn, entry_attrs, keys, options) + try: + _entry_attrs = ldap.get_entry(dn, self.obj.default_attributes) + except errors.NotFound: + self.obj.handle_not_found(*keys) + + if 'hostmask' in options: + norm = lambda x: unicode(netaddr.IPNetwork(x).cidr) + + old_masks = set(map(norm, _entry_attrs.get('hostmask', []))) + new_masks = set(map(norm, options['hostmask'])) + + num_added = len(new_masks - old_masks) + + if num_added: + entry_attrs['hostmask'] = list(old_masks | new_masks) + try: + ldap.update_entry(entry_attrs) + except errors.EmptyModlist: + pass + completed = completed + num_added + + return add_external_post_callback('memberhost', 'host', 'externalhost', + ldap, completed, failed, dn, + entry_attrs, keys, options) @@ -604,9 +647,35 @@ class sudorule_remove_host(LDAPRemoveMember): member_attributes = ['memberhost'] member_count_out = ('%i object removed.', '%i objects removed.') + def get_options(self): + for option in super(sudorule_remove_host, self).get_options(): + yield option + yield hostmask_membership_param def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): assert isinstance(dn, DN) + + try: + _entry_attrs = ldap.get_entry(dn, self.obj.default_attributes) + except errors.NotFound: + self.obj.handle_not_found(*keys) + + if 'hostmask' in options: + norm = lambda x: unicode(netaddr.IPNetwork(x).cidr) + + old_masks = set(map(norm, _entry_attrs.get('hostmask', []))) + removed_masks = set(map(norm, options['hostmask'])) + + num_added = len(removed_masks & old_masks) + + if num_added: + entry_attrs['hostmask'] = list(old_masks - removed_masks) + try: + ldap.update_entry(entry_attrs) + except errors.EmptyModlist: + pass + completed = completed + num_added + return remove_external_post_callback('memberhost', 'host', 'externalhost', ldap, completed, failed, dn, entry_attrs, keys, diff --git a/ipalib/util.py b/ipalib/util.py index 265957949..ef759d8d1 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -32,6 +32,7 @@ from types import NoneType from weakref import WeakKeyDictionary from dns import resolver, rdatatype from dns.exception import DNSException +from netaddr.core import AddrFormatError from ipalib import errors from ipalib.text import _ @@ -544,3 +545,9 @@ def validate_rdn_param(ugettext, value): except Exception, e: return str(e) return None + +def validate_hostmask(ugettext, hostmask): + try: + netaddr.IPNetwork(hostmask) + except (ValueError, AddrFormatError): + return _('invalid hostmask') -- cgit