summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--API.txt6
-rw-r--r--install/share/schema_compat.uldif1
-rw-r--r--install/updates/10-schema_compat.update3
-rw-r--r--ipalib/plugins/sudorule.py73
-rw-r--r--ipalib/util.py7
5 files changed, 86 insertions, 4 deletions
diff --git a/API.txt b/API.txt
index 0dd28068e..fdb780d53 100644
--- a/API.txt
+++ b/API.txt
@@ -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')
diff --git a/install/share/schema_compat.uldif b/install/share/schema_compat.uldif
index 9a9607eeb..3d2681d64 100644
--- a/install/share/schema_compat.uldif
+++ b/install/share/schema_compat.uldif
@@ -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")'
diff --git a/install/updates/10-schema_compat.update b/install/updates/10-schema_compat.update
index 505bfcaa8..c45734c55 100644
--- a/install/updates/10-schema_compat.update
+++ b/install/updates/10-schema_compat.update
@@ -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
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 <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,
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')