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 <pviktori@redhat.com>
This commit is contained in:
Tomas Babej
2014-05-14 12:52:26 +02:00
committed by Petr Viktorin
parent 5a1207cb6e
commit a228d7a3cb
5 changed files with 86 additions and 4 deletions

View File

@@ -3474,11 +3474,12 @@ output: Output('completed', <type 'int'>, None)
output: Output('failed', <type 'dict'>, None)
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
command: sudorule_add_host
args: 1,6,3
args: 1,7,3
arg: Str('cn', attribute=True, cli_name='sudorule_name', multivalue=False, primary_key=True, query=True, required=True)
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
option: Str('host*', alwaysask=True, cli_name='hosts', csv=True)
option: Str('hostgroup*', alwaysask=True, cli_name='hostgroups', csv=True)
option: Str('hostmask?', multivalue=True)
option: Flag('no_members', autofill=True, default=False, exclude='webui')
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('version?', exclude='webui')
@@ -3627,11 +3628,12 @@ output: Output('completed', <type 'int'>, None)
output: Output('failed', <type 'dict'>, None)
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
command: sudorule_remove_host
args: 1,6,3
args: 1,7,3
arg: Str('cn', attribute=True, cli_name='sudorule_name', multivalue=False, primary_key=True, query=True, required=True)
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
option: Str('host*', alwaysask=True, cli_name='hosts', csv=True)
option: Str('hostgroup*', alwaysask=True, cli_name='hostgroups', csv=True)
option: Str('hostmask?', multivalue=True)
option: Flag('no_members', autofill=True, default=False, exclude='webui')
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('version?', exclude='webui')

View File

@@ -86,6 +86,7 @@ add:schema-compat-entry-attribute: 'sudoHost=%ifeq("hostCategory","all","ALL","%
add:schema-compat-entry-attribute: 'sudoHost=%ifeq("hostCategory","all","ALL","%deref_rf(\"memberHost\",\"(&(objectclass=ipaHostGroup)(!(objectclass=mepOriginEntry)))\",\"member\",\"(|(objectclass=ipaHostGroup)(objectclass=ipaHost))\",\"fqdn\")")'
add:schema-compat-entry-attribute: 'sudoHost=%ifeq("hostCategory","all","ALL","+%deref_f(\"memberHost\",\"(&(objectclass=ipaHostGroup)(objectclass=mepOriginEntry))\",\"cn\")")'
add:schema-compat-entry-attribute: 'sudoHost=%ifeq("hostCategory","all","ALL","+%deref_f(\"memberHost\",\"(objectclass=ipaNisNetgroup)\",\"cn\")")'
add:schema-compat-entry-attribute: 'sudoHost=%ifeq("hostCategory","all","ALL","%{hostMask}")'
add:schema-compat-entry-attribute: 'sudoCommand=%ifeq("cmdCategory","all","ALL","%deref(\"memberAllowCmd\",\"sudoCmd\")")'
add:schema-compat-entry-attribute: 'sudoCommand=%ifeq("cmdCategory","all","ALL","%deref_r(\"memberAllowCmd\",\"member\",\"sudoCmd\")")'
add:schema-compat-entry-attribute: 'sudoCommand=!%deref("memberDenyCmd","sudoCmd")'

View File

@@ -2,6 +2,9 @@ dn: cn=sudoers,cn=Schema Compatibility,cn=plugins,cn=config
only:schema-compat-entry-rdn:'%ifeq("ipaEnabledFlag", "FALSE", "DISABLED", "cn=%{cn}")'
replace: schema-compat-entry-attribute:'sudoRunAsGroup=%deref("ipaSudoRunAs","cn")::sudoRunAsGroup=%deref_f("ipaSudoRunAsGroup","(objectclass=posixGroup)","cn")'
dn: cn=sudoers,cn=Schema Compatibility,cn=plugins,cn=config
add:schema-compat-entry-attribute: 'sudoHost=%ifeq("hostCategory","all","ALL","%{hostMask}")'
# Change padding for host and userCategory so the pad returns the same value
# as the original, '' or -.
dn: cn=ng,cn=Schema Compatibility,cn=plugins,cn=config

View File

@@ -17,6 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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,

View File

@@ -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')