From 6348f198b4dd64689a2350847255ed453cdcfbd3 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 18 Jan 2013 09:26:35 -0500 Subject: POSIXUsers: set managed uid/gid range Added options to set a range (whitelist or blacklist) of managed uids/gids so that accounts in LDAP (e.g.) do not get flagged as "extra" entries. Request: http://article.gmane.org/gmane.comp.sysutils.bcfg2.devel/4629 --- src/lib/Bcfg2/Client/Tools/POSIXUsers.py | 97 +++++++++++++++++++++++++++++--- src/lib/Bcfg2/Options.py | 26 ++++++++- 2 files changed, 115 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py index 78734f5c2..7c8a4d578 100644 --- a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py +++ b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py @@ -4,9 +4,45 @@ and groupadd/mod/del """ import sys import pwd import grp -import Bcfg2.Client.XML import subprocess +import Bcfg2.Client.XML import Bcfg2.Client.Tools +from Bcfg2.Compat import any # pylint: disable=W0622 + + +class IDRangeSet(object): + """ Representation of a set of integer ranges. Used to describe + which UID/GID ranges are managed or unmanaged. """ + + def __init__(self, *ranges): + self.ranges = [] + self.ints = [] + self.str = ",".join(str(r) for r in ranges) + for item in ranges: + item = str(item).strip() + if item.endswith("-"): + self.ranges.append((int(item[:-1]), None)) + elif '-' in str(item): + self.ranges.append(tuple(int(x) for x in item.split('-'))) + else: + self.ints.append(int(item)) + + def __contains__(self, other): + other = int(other) + if other in self.ints: + return True + return any((end is None and other >= start) or + (end is not None and other >= start and other <= end) + for start, end in self.ranges) + + def __repr__(self): + return "%s:%s" % (self.__class__.__name__, str(self)) + + def __str__(self): + return "[%s]" % self.str + + def __len__(self): + return len(self.ranges) + len(self.ints) class ExecutionError(Exception): @@ -68,18 +104,36 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool): POSIXGroup=['name']) experimental = True - # A mapping of XML entry attributes to the indexes of - # corresponding values in the get*ent data structures + #: A mapping of XML entry attributes to the indexes of + #: corresponding values in the get{pw|gr}all data structures attr_mapping = dict(POSIXUser=dict(name=0, uid=2, gecos=4, home=5, shell=6), POSIXGroup=dict(name=0, gid=2)) + #: A mapping that describes the attribute name of the id of a given + #: user or group + id_mapping = dict(POSIXUser="uid", POSIXGroup="gid") + def __init__(self, logger, setup, config): Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config) self.set_defaults = dict(POSIXUser=self.populate_user_entry, POSIXGroup=lambda g: g) self.cmd = Executor(logger) self._existing = None + self._whitelist = dict(POSIXUser=None, POSIXGroup=None) + self._blacklist = dict(POSIXUser=None, POSIXGroup=None) + if self.setup['posix_uid_whitelist']: + self._whitelist['POSIXUser'] = \ + IDRangeSet(*self.setup['posix_uid_whitelist']) + else: + self._blacklist['POSIXUser'] = \ + IDRangeSet(*self.setup['posix_uid_blacklist']) + if self.setup['posix_gid_whitelist']: + self._whitelist['POSIXGroup'] = \ + IDRangeSet(*self.setup['posix_gid_whitelist']) + else: + self._blacklist['POSIXGroup'] = \ + IDRangeSet(*self.setup['posix_gid_blacklist']) @property def existing(self): @@ -91,6 +145,33 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool): for g in grp.getgrall()])) return self._existing + def _in_managed_range(self, tag, eid): + """ Check if the given uid or gid is in the appropriate + managed range. This means that either a) a whitelist is + defined, and the uid/gid is in that whitelist; or b) no + whitelist is defined, and the uid/gid is not in the + blacklist. """ + if self._whitelist[tag] is None: + return eid not in self._blacklist[tag] + else: + return eid in self._whitelist[tag] + + def canInstall(self, entry): + if not Bcfg2.Client.Tools.Tool.canInstall(self, entry): + return False + eid = entry.get(self.id_mapping[entry.tag]) + if eid is not None and not self._in_managed_range(entry.tag, eid): + if self._whitelist[entry.tag] is not None: + err = "not in whitelist" + else: # blacklisted + err = "in blacklist" + self.logger.debug("%s: %s %s %s: %s" % + (self.primarykey(entry), err, + self.id_mapping[entry.tag], eid, + self._blacklist[entry.tag])) + return False + return True + def Inventory(self, states, structures=None): if not structures: structures = self.config.getchildren() @@ -121,9 +202,11 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool): for entry in self.getSupportedEntries(): if entry.tag == tag: specified.append(entry.get("name")) - extra.extend([Bcfg2.Client.XML.Element(tag, name=e) - for e in self.existing[tag].keys() - if e not in specified]) + for name, data in self.existing[tag].items(): + eid = data[self.attr_mapping[tag][self.id_mapping[tag]]] + if name not in specified and self._in_managed_range(tag, eid): + extra.append(Bcfg2.Client.XML.Element(tag, name=name)) + return extra def populate_user_entry(self, entry): @@ -201,7 +284,7 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool): errors.append("%s for %s %s is incorrect. Current %s is " "%s, but should be %s" % (attr.title(), entry.tag, entry.get("name"), - attr, entry.get(attr), val)) + attr, val, entry.get(attr))) if errors: for error in errors: diff --git a/src/lib/Bcfg2/Options.py b/src/lib/Bcfg2/Options.py index c0a274e23..07d089f05 100644 --- a/src/lib/Bcfg2/Options.py +++ b/src/lib/Bcfg2/Options.py @@ -989,6 +989,26 @@ CLIENT_YUM_VERIFY_FLAGS = \ cf=('YUM', 'verify_flags'), deprecated_cf=('YUMng', 'verify_flags'), cook=list_split) +CLIENT_POSIX_UID_WHITELIST = \ + Option("UID ranges the POSIXUsers tool will manage", + default=[], + cf=('POSIXUsers', 'uid_whitelist'), + cook=list_split) +CLIENT_POSIX_GID_WHITELIST = \ + Option("GID ranges the POSIXUsers tool will manage", + default=[], + cf=('POSIXUsers', 'gid_whitelist'), + cook=list_split) +CLIENT_POSIX_UID_BLACKLIST = \ + Option("UID ranges the POSIXUsers tool will not manage", + default=[], + cf=('POSIXUsers', 'uid_blacklist'), + cook=list_split) +CLIENT_POSIX_GID_BLACKLIST = \ + Option("GID ranges the POSIXUsers tool will not manage", + default=[], + cf=('POSIXUsers', 'gid_blacklist'), + cook=list_split) # Logging options LOGGING_FILE_PATH = \ @@ -1133,7 +1153,11 @@ DRIVER_OPTIONS = \ yum_installed_action=CLIENT_YUM_INSTALLED_ACTION, yum_version_fail_action=CLIENT_YUM_VERSION_FAIL_ACTION, yum_verify_fail_action=CLIENT_YUM_VERIFY_FAIL_ACTION, - yum_verify_flags=CLIENT_YUM_VERIFY_FLAGS) + yum_verify_flags=CLIENT_YUM_VERIFY_FLAGS, + posix_uid_whitelist=CLIENT_POSIX_UID_WHITELIST, + posix_gid_whitelist=CLIENT_POSIX_UID_WHITELIST, + posix_uid_blacklist=CLIENT_POSIX_UID_BLACKLIST, + posix_gid_blacklist=CLIENT_POSIX_UID_BLACKLIST) CLIENT_COMMON_OPTIONS = \ dict(extra=CLIENT_EXTRA_DISPLAY, -- cgit v1.2.3-1-g7c22 From 528184be255835c455c69c4754a09dbe456a9139 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 18 Jan 2013 09:28:55 -0500 Subject: GroupPatterns: improved PackedDigitRange and tests --- src/lib/Bcfg2/Server/Plugins/GroupPatterns.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/lib/Bcfg2/Server/Plugins/GroupPatterns.py b/src/lib/Bcfg2/Server/Plugins/GroupPatterns.py index 2e8c56b4e..1b12e590a 100644 --- a/src/lib/Bcfg2/Server/Plugins/GroupPatterns.py +++ b/src/lib/Bcfg2/Server/Plugins/GroupPatterns.py @@ -6,6 +6,7 @@ import sys import logging import Bcfg2.Server.Lint import Bcfg2.Server.Plugin +from Bcfg2.Compat import any # pylint: disable=W0622 class PackedDigitRange(object): @@ -25,10 +26,8 @@ class PackedDigitRange(object): iother = int(other) if iother in self.sparse: return True - for (start, end) in self.ranges: - if iother in range(start, end + 1): - return True - return False + return any(iother in range(start, end + 1) + for start, end in self.ranges) class PatternMap(object): -- cgit v1.2.3-1-g7c22 From 3d78a3a1c00035c9d8c49b949b63e8f05f31c7a1 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 18 Jan 2013 11:04:33 -0500 Subject: Properties: fixed lax/strict decryption setting with no crypto libs installed --- src/lib/Bcfg2/Server/Plugins/Properties.py | 40 ++++++++++++++---------------- 1 file changed, 19 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/lib/Bcfg2/Server/Plugins/Properties.py b/src/lib/Bcfg2/Server/Plugins/Properties.py index a51dd8adc..3ebad40e3 100644 --- a/src/lib/Bcfg2/Server/Plugins/Properties.py +++ b/src/lib/Bcfg2/Server/Plugins/Properties.py @@ -205,27 +205,25 @@ class XMLPropertyFile(Bcfg2.Server.Plugin.StructFile, PropertyFile): def Index(self): Bcfg2.Server.Plugin.StructFile.Index(self) - strict = self.xdata.get( - "decrypt", - SETUP.cfp.get(Bcfg2.Encryption.CFG_SECTION, "decrypt", - default="strict")) == "strict" - for el in self.xdata.xpath("//*[@encrypted]"): - if not HAS_CRYPTO: - raise PluginExecutionError("Properties: M2Crypto is not " - "available: %s" % self.name) - try: - el.text = self._decrypt(el).encode('ascii', - 'xmlcharrefreplace') - except UnicodeDecodeError: - LOGGER.info("Properties: Decrypted %s to gibberish, " - "skipping" % el.tag) - except Bcfg2.Encryption.EVPError: - msg = "Properties: Failed to decrypt %s element in %s" % \ - (el.tag, self.name) - if strict: - raise PluginExecutionError(msg) - else: - LOGGER.warning(msg) + if HAS_CRYPTO: + strict = self.xdata.get( + "decrypt", + SETUP.cfp.get(Bcfg2.Encryption.CFG_SECTION, "decrypt", + default="strict")) == "strict" + for el in self.xdata.xpath("//*[@encrypted]"): + try: + el.text = self._decrypt(el).encode('ascii', + 'xmlcharrefreplace') + except UnicodeDecodeError: + LOGGER.info("Properties: Decrypted %s to gibberish, " + "skipping" % el.tag) + except Bcfg2.Encryption.EVPError: + msg = "Properties: Failed to decrypt %s element in %s" % \ + (el.tag, self.name) + if strict: + raise PluginExecutionError(msg) + else: + LOGGER.warning(msg) Index.__doc__ = Bcfg2.Server.Plugin.StructFile.Index.__doc__ def _decrypt(self, element): -- cgit v1.2.3-1-g7c22 From c2133f115673670992048f3567c22e7478281a79 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 18 Jan 2013 11:06:41 -0500 Subject: StructFile: fixed lax/strict decryption setting with no crypto libs installed --- src/lib/Bcfg2/Server/Plugin/helpers.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 399ab6679..b036fc31d 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -598,15 +598,12 @@ class StructFile(XMLFileBacked): def Index(self): XMLFileBacked.Index(self) - if self.encryption: + if self.encryption and HAS_CRYPTO: strict = self.xdata.get( "decrypt", self.setup.cfp.get(Bcfg2.Encryption.CFG_SECTION, "decrypt", default="strict")) == "strict" for el in self.xdata.xpath("//*[@encrypted]"): - if not HAS_CRYPTO: - raise PluginExecutionError("%s: M2Crypto is not available" - % self.name) try: el.text = self._decrypt(el).encode('ascii', 'xmlcharrefreplace') -- cgit v1.2.3-1-g7c22