diff options
-rw-r--r-- | doc/development/client-driver.txt | 2 | ||||
-rw-r--r-- | doc/server/plugins/generators/packages.txt | 38 | ||||
-rw-r--r-- | doc/server/plugins/generators/sslca.txt | 8 | ||||
-rw-r--r-- | doc/server/plugins/probes/index.txt | 2 | ||||
-rw-r--r-- | schemas/packages.xsd | 7 | ||||
-rw-r--r-- | solaris/Makefile | 5 | ||||
-rw-r--r-- | solaris/gen-prototypes.sh | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Bcfg2Py3k.py | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/POSIX.py | 48 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Core.py | 8 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugin.py | 47 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/NagiosGen.py | 7 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Packages/Source.py | 28 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Packages/Yum.py | 15 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Packages/__init__.py | 4 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Probes.py | 53 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/SSLCA.py | 105 | ||||
-rwxr-xr-x | src/sbin/bcfg2-info | 3 |
18 files changed, 232 insertions, 152 deletions
diff --git a/doc/development/client-driver.txt b/doc/development/client-driver.txt index 32bb0aff4..c42d2b964 100644 --- a/doc/development/client-driver.txt +++ b/doc/development/client-driver.txt @@ -20,7 +20,7 @@ an existing driver, and the process that was used to create it. * Otherwise, subclass ``Bcfg2.Client.Tools.Tool`` (from here referenced as branch [T]) -#. Set ``__name__`` to "RPM" +#. Set ``name`` to "RPM" #. Add any required executable programs to ``__execs__`` #. Set ``__handles__`` to a list of (**entry.tag**, **entry.get('type')**) tuples. This determines which entries the Tool module can be used diff --git a/doc/server/plugins/generators/packages.txt b/doc/server/plugins/generators/packages.txt index b29752270..38952de3e 100644 --- a/doc/server/plugins/generators/packages.txt +++ b/doc/server/plugins/generators/packages.txt @@ -182,10 +182,40 @@ With the keys specified thusly, Packages will include the keys in the generated yum config file, and will ensure that the keys are imported on the client. -There is no need to specify ``<GPGKey>`` tags for :ref:``Pulp sources -<pulp-source-support>``; that data is pulled directly from the Pulp +There is no need to specify ``<GPGKey>`` tags for :ref:`Pulp sources +<pulp-source-support>`; that data is pulled directly from the Pulp REST API. +Arbitrary Repo Options +---------------------- + +.. versionadded:: 1.2.3 + +You can specify arbitrary options to be added to the repository config +on the server side, if you are using the native yum libraries, and on +the client side if you are using the ability of Packages to +automatically generate your Yum config. To do this, add an +``<Options>`` tag to a Source; all of its attributes will be added +verbatim to the repository in the generated config. For instance:: + + <Source type="yum" rawurl="http://mirror.example.com/centos-6-os"> + <Arch>x86_64</Arch> + <Options proxy="http://proxy.example.com"/> + </Source> + +If you are using native yum libraries and need to set options only on +the Bcfg2 server, you can set the ``serveronly`` attribute to "true"; +or, if you need to set options only on the client, you can set the +``clientonly`` attribute to "true". For instance, if your Bcfg2 +server needed to use a proxy to access a repo, and you wanted to +expire metadata caches very quickly on the client, you could do:: + + <Source type="yum" rawurl="http://mirror.example.com/centos-6-os"> + <Arch>x86_64</Arch> + <Options serveronly="true" proxy="http://proxy.example.com"/> + <Options clientonly="true" metadata_expire="0"/> + </Source> + .. _packages-exampleusage: Example usage @@ -258,8 +288,8 @@ Yum sources can be similarly specified: For sources with a **URL** attribute, the **Version** attribute is also necessary. -:ref:``Pulp sources <pulp-source-support>`` are very simple to specify -due to the amount of data that can be queried from Pulp itself: +:ref:`Pulp sources <pulp-source-support>` are very simple to specify +due to the amount of data that can be queried from Pulp itself:: .. code-block::xml diff --git a/doc/server/plugins/generators/sslca.txt b/doc/server/plugins/generators/sslca.txt index 8e33148cb..d2b051535 100644 --- a/doc/server/plugins/generators/sslca.txt +++ b/doc/server/plugins/generators/sslca.txt @@ -33,7 +33,7 @@ must contain full (not relative) paths. #. Add SSLCA to the **plugins** line in ``/etc/bcfg2.conf`` and restart the server -- This enabled the SSLCA plugin on the Bcfg2 server. -#. Add a section to your ``/etc/bcfg2.conf`` called sslca_foo, replacing foo +#. Add a section to your ``/etc/bcfg2.conf`` called ``sslca_foo``, replacing foo with the name you wish to give your CA so you can reference it in certificate definitions. @@ -51,6 +51,12 @@ must contain full (not relative) paths. specification. If you're using a self signing CA this would be the CA cert that you generated. +#. Optionally, add ``verify_certs = false`` if you don't wish to + perform certificate verification on the certs SSLCA generates. + Verification includes ``openssl verify`` to verify the CA chain, + and ensuring that both the key file and certificate file contain + the same key. + #. Once all this is done, you should have a section in your ``/etc/bcfg2.conf`` that looks similar to the following:: diff --git a/doc/server/plugins/probes/index.txt b/doc/server/plugins/probes/index.txt index 95aa2d0ce..cacc42bc1 100644 --- a/doc/server/plugins/probes/index.txt +++ b/doc/server/plugins/probes/index.txt @@ -211,7 +211,7 @@ look something like: <FileProbe name="/etc/blah.conf" update="true"/> </Group> <Client name="bar.example.com"> - <FileProbe name="/var/lib/bar.gz" base64="true"/> + <FileProbe name="/var/lib/bar.gz" encoding="base64"/> </Client> </FileProbes> diff --git a/schemas/packages.xsd b/schemas/packages.xsd index c29a85ecf..c4252194f 100644 --- a/schemas/packages.xsd +++ b/schemas/packages.xsd @@ -18,11 +18,18 @@ </xsd:restriction> </xsd:simpleType> + <xsd:complexType name="RepoOptionsType"> + <xsd:attribute type="xsd:boolean" name="serveronly"/> + <xsd:attribute type="xsd:boolean" name="clientonly"/> + <xsd:anyAttribute processContents="lax"/> + </xsd:complexType> + <xsd:complexType name="sourceType"> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element name="Component" type="xsd:string"/> <xsd:element name="Arch" type="xsd:string"/> <xsd:element name="GPGKey" type="xsd:string"/> + <xsd:element name="Options" type="RepoOptionsType"/> <xsd:choice> <xsd:element name="Blacklist" type="xsd:string"/> <xsd:element name="Whitelist" type="xsd:string"/> diff --git a/solaris/Makefile b/solaris/Makefile index 77d9019eb..9ca87fc48 100644 --- a/solaris/Makefile +++ b/solaris/Makefile @@ -12,8 +12,9 @@ package: -cd ../ && PYTHONPATH=$(PYTHONPATH):$(PWD)/build/lib/python2.6/site-packages/ $(PYTHON) setup.py install --single-version-externally-managed --record=/dev/null --prefix=$(PWD)/build #setuptools appears to use a restictive umask -chmod -R o+r build/ - -cat bin/bcfg2 | sed -e 's!/usr/bin/python!$(PYTHON)!' > bin/bcfg2.new && mv bin/bcfg2.new bin/bcfg2 - -./gen-prototypes.sh + -cat build/bin/bcfg2 | sed -e 's!/usr/bin/python!$(PYTHON)!' > build/bin/bcfg2.new && mv build/bin/bcfg2.new build/bin/bcfg2 + -chmod +x build/bin/bcfg2 + -sh ./gen-prototypes.sh -pkgmk -o -a `uname -m` -f prototype.bcfg2 -d $(PWD)/tmp -r $(PWD)/build -pkgmk -o -a `uname -m` -f prototype.bcfg2-server -d $(PWD)/tmp -r $(PWD)/build -pkgtrans -o -s $(PWD)/tmp $(PWD)/bcfg2-$(VERS) SCbcfg2 diff --git a/solaris/gen-prototypes.sh b/solaris/gen-prototypes.sh index ea0b4bb13..64aff9edb 100644 --- a/solaris/gen-prototypes.sh +++ b/solaris/gen-prototypes.sh @@ -1,6 +1,6 @@ #!/bin/sh cd build -PP="./"`ls -1d lib/*`"/site-packages/" +PP="./lib/python/site-packages/" #bcfg2 echo "i pkginfo=./pkginfo.bcfg2" > ../prototype.tmp diff --git a/src/lib/Bcfg2/Bcfg2Py3k.py b/src/lib/Bcfg2/Bcfg2Py3k.py index 5b74d4225..30feaf8c5 100644 --- a/src/lib/Bcfg2/Bcfg2Py3k.py +++ b/src/lib/Bcfg2/Bcfg2Py3k.py @@ -14,6 +14,7 @@ try: from urllib2 import install_opener from urllib2 import urlopen from urllib2 import HTTPError + from urllib2 import URLError except ImportError: from urllib.parse import urljoin, urlparse from urllib.request import HTTPBasicAuthHandler @@ -22,6 +23,7 @@ except ImportError: from urllib.request import install_opener from urllib.request import urlopen from urllib.error import HTTPError + from urllib.error import URLError try: from cStringIO import StringIO diff --git a/src/lib/Bcfg2/Client/Tools/POSIX.py b/src/lib/Bcfg2/Client/Tools/POSIX.py index 519e6b232..995d82356 100644 --- a/src/lib/Bcfg2/Client/Tools/POSIX.py +++ b/src/lib/Bcfg2/Client/Tools/POSIX.py @@ -45,35 +45,39 @@ def calcPerms(initial, perms): return tempperms -def normGid(entry): +def normGid(entry, logger=None): """ This takes a group name or gid and returns the corresponding gid or False. """ + if logger is None: + logger = log try: try: return int(entry.get('group')) except: return int(grp.getgrnam(entry.get('group'))[2]) except (OSError, KeyError): - log.error('GID normalization failed for %s. Does group %s exist?' - % (entry.get('name'), entry.get('group'))) + logger.error('GID normalization failed for %s. Does group %s exist?' % + (entry.get('name'), entry.get('group'))) return False -def normUid(entry): +def normUid(entry, logger=None): """ This takes a user name or uid and returns the corresponding uid or False. """ + if logger is None: + logger = log try: try: return int(entry.get('owner')) except: return int(pwd.getpwnam(entry.get('owner'))[2]) except (OSError, KeyError): - log.error('UID normalization failed for %s. Does owner %s exist?' - % (entry.get('name'), entry.get('owner'))) + logger.error('UID normalization failed for %s. Does owner %s exist?' % + (entry.get('name'), entry.get('owner'))) return False @@ -172,8 +176,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): dev_type = entry.get('dev_type') mode = calcPerms(device_map[dev_type], entry.get('mode', '0600')) - owner = normUid(entry) - group = normGid(entry) + owner = normUid(entry, logger=self.logger) + group = normGid(entry, logger=self.logger) if dev_type in ['block', 'char']: # check for incompletely specified entries if entry.get('major') == None or \ @@ -245,7 +249,9 @@ class POSIX(Bcfg2.Client.Tools.Tool): are set as specified by the user. """ os.chmod(entry.get('name'), mode) - os.chown(entry.get('name'), normUid(entry), normGid(entry)) + os.chown(entry.get('name'), + normUid(entry, logger=self.logger), + normGid(entry, logger=self.logger)) return True except KeyError: self.logger.error('Failed to install %s' % entry.get('name')) @@ -284,8 +290,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): mtime = str(finfo[stat.ST_MTIME]) else: mtime = '-1' - pTrue = ((owner == str(normUid(entry))) and - (group == str(normGid(entry))) and + pTrue = ((owner == str(normUid(entry, logger=self.logger))) and + (group == str(normGid(entry, logger=self.logger))) and (perms == entry.get('perms')) and (mtime == entry.get('mtime', '-1'))) @@ -315,7 +321,7 @@ class POSIX(Bcfg2.Client.Tools.Tool): pruneTrue = True if not pTrue: - if owner != str(normUid(entry)): + if owner != str(normUid(entry, logger=self.logger)): entry.set('current_owner', owner) self.logger.debug("%s %s ownership wrong" % \ (entry.tag, entry.get('name'))) @@ -323,7 +329,7 @@ class POSIX(Bcfg2.Client.Tools.Tool): nqtext += "%s owner wrong. is %s should be %s" % \ (entry.get('name'), owner, entry.get('owner')) entry.set('qtext', nqtext) - if group != str(normGid(entry)): + if group != str(normGid(entry, logger=self.logger)): entry.set('current_group', group) self.logger.debug("%s %s group wrong" % \ (entry.tag, entry.get('name'))) @@ -651,7 +657,9 @@ class POSIX(Bcfg2.Client.Tools.Tool): newfile.write(filedata) newfile.close() try: - os.chown(newfile.name, normUid(entry), normGid(entry)) + os.chown(newfile.name, + normUid(entry, logger=self.logger), + normGid(entry, logger=self.logger)) except KeyError: self.logger.error("Failed to chown %s to %s:%s" % (newfile.name, entry.get('owner'), @@ -789,8 +797,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): return False if entry.get('recursive') in ['True', 'true']: # verify ownership information recursively - owner = normUid(entry) - group = normGid(entry) + owner = normUid(entry, logger=self.logger) + group = normGid(entry, logger=self.logger) for root, dirs, files in os.walk(entry.get('name')): for p in dirs + files: @@ -859,8 +867,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): plist = [entry.get('name')] if entry.get('recursive') in ['True', 'true']: # verify ownership information recursively - owner = normUid(entry) - group = normGid(entry) + owner = normUid(entry, logger=self.logger) + group = normGid(entry, logger=self.logger) for root, dirs, files in os.walk(entry.get('name')): for p in dirs + files: @@ -871,7 +879,9 @@ class POSIX(Bcfg2.Client.Tools.Tool): plist.append(path) try: for p in plist: - os.chown(p, normUid(entry), normGid(entry)) + os.chown(p, + normUid(entry, logger=self.logger), + normGid(entry, logger=self.logger)) os.chmod(p, calcPerms(stat.S_IFDIR, entry.get('perms'))) return True except (OSError, KeyError): diff --git a/src/lib/Bcfg2/Server/Core.py b/src/lib/Bcfg2/Server/Core.py index cbea87335..e8ebd511d 100644 --- a/src/lib/Bcfg2/Server/Core.py +++ b/src/lib/Bcfg2/Server/Core.py @@ -82,11 +82,9 @@ class Core(Component): try: self.fam = fm(**famargs) except IOError: - logger.error("Failed to instantiate fam driver %s" % filemonitor, - exc_info=1) - raise CoreInitError("Failed to instantiate fam driver (used %s)" % - filemonitor) - + msg = "Failed to instantiate fam driver %s" % filemonitor + logger.error(msg, exc_info=1) + raise CoreInitError(msg) self.pubspace = {} self.cfile = cfile self.cron = {} diff --git a/src/lib/Bcfg2/Server/Plugin.py b/src/lib/Bcfg2/Server/Plugin.py index a68a9e80e..fb5e115a9 100644 --- a/src/lib/Bcfg2/Server/Plugin.py +++ b/src/lib/Bcfg2/Server/Plugin.py @@ -9,10 +9,9 @@ import posixpath import re import sys import threading +import Bcfg2.Server from Bcfg2.Bcfg2Py3k import ConfigParser -from lxml.etree import XML, XMLSyntaxError - import Bcfg2.Options # py3k compatibility @@ -55,6 +54,19 @@ info_regex = re.compile( \ 'perms:(\s)*(?P<perms>\w+)|' + 'sensitive:(\s)*(?P<sensitive>\S+)|') +def bind_info(entry, metadata, infoxml=None, default=default_file_metadata): + for attr, val in list(default.items()): + entry.set(attr, val) + if infoxml: + mdata = dict() + infoxml.pnode.Match(metadata, mdata, entry=entry) + if 'Info' not in mdata: + msg = "Failed to set metadata for file %s" % entry.get('name') + logger.error(msg) + raise PluginExecutionError(msg) + for attr, val in list(mdata['Info'][None].items()): + entry.set(attr, val) + class PluginInitError(Exception): """Error raised in cases of Plugin initialization errors.""" @@ -265,7 +277,9 @@ class ThreadedStatistics(Statistics, if self.terminate.isSet(): return False - self.work_queue.put_nowait((metadata, lxml.etree.fromstring(pdata))) + self.work_queue.put_nowait((metadata, + lxml.etree.XML(pdata, + parser=Bcfg2.Server.XMLParser))) except Full: self.logger.warning("Queue.Full: Failed to load queue data") break @@ -284,7 +298,7 @@ class ThreadedStatistics(Statistics, def run(self): if not self.load(): return - while not self.terminate.isSet(): + while not self.terminate.isSet() and self.work_queue != None: try: (xdata, client) = self.work_queue.get(block=True, timeout=2) except Empty: @@ -294,7 +308,7 @@ class ThreadedStatistics(Statistics, self.logger.error("ThreadedStatistics: %s" % e) continue self.handle_statistic(xdata, client) - if not self.work_queue.empty(): + if self.work_queue != None and not self.work_queue.empty(): self.save() def process_statistics(self, metadata, data): @@ -583,8 +597,9 @@ class XMLFileBacked(FileBacked): def Index(self): """Build local data structures.""" try: - self.xdata = XML(self.data) - except XMLSyntaxError: + self.xdata = lxml.etree.XML(self.data, + parser=Bcfg2.Server.XMLParser) + except lxml.etree.XMLSyntaxError: logger.error("Failed to parse %s" % (self.name)) return self.entries = self.xdata.getchildren() @@ -635,7 +650,8 @@ class SingleXMLFileBacked(XMLFileBacked): def Index(self): """Build local data structures.""" try: - self.xdata = lxml.etree.XML(self.data, base_url=self.name) + self.xdata = lxml.etree.XML(self.data, base_url=self.name, + parser=Bcfg2.Server.XMLParser) except lxml.etree.XMLSyntaxError: err = sys.exc_info()[1] logger.error("Failed to parse %s: %s" % (self.name, err)) @@ -793,7 +809,7 @@ class XMLSrc(XMLFileBacked): return self.items = {} try: - xdata = lxml.etree.XML(data) + xdata = lxml.etree.XML(data, parser=Bcfg2.Server.XMLParser) except lxml.etree.XMLSyntaxError: logger.error("Failed to parse file %s" % (self.name)) return @@ -1118,18 +1134,7 @@ class EntrySet(Debuggable): return cmp(x.specific.prio, y.specific.prio) def bind_info_to_entry(self, entry, metadata): - # first set defaults from global metadata/:info - for key in self.metadata: - entry.set(key, self.metadata[key]) - if self.infoxml: - mdata = {} - self.infoxml.pnode.Match(metadata, mdata, entry=entry) - if 'Info' not in mdata: - logger.error("Failed to set metadata for file %s" % \ - (entry.get('name'))) - raise PluginExecutionError - [entry.attrib.__setitem__(key, value) \ - for (key, value) in list(mdata['Info'][None].items())] + bind_info(entry, metadata, infoxml=self.infoxml, default=self.metadata) def bind_entry(self, entry, metadata): """Return the appropriate interpreted template from the set of available templates.""" diff --git a/src/lib/Bcfg2/Server/Plugins/NagiosGen.py b/src/lib/Bcfg2/Server/Plugins/NagiosGen.py index 0c7b1daf7..4e8b09f30 100644 --- a/src/lib/Bcfg2/Server/Plugins/NagiosGen.py +++ b/src/lib/Bcfg2/Server/Plugins/NagiosGen.py @@ -65,7 +65,12 @@ class NagiosGen(Bcfg2.Server.Plugin.Plugin, def createhostconfig(self, entry, metadata): """Build host specific configuration file.""" - host_address = socket.gethostbyname(metadata.hostname) + try: + host_address = socket.gethostbyname(metadata.hostname) + except socket.gaierror: + LOGGER.error("Failed to find IP address for %s" % + metadata.hostname) + raise Bcfg2.Server.Plugin.PluginExecutionError host_groups = [grp for grp in metadata.groups if os.path.isfile('%s/%s-group.cfg' % (self.data, grp))] host_config = ['define host {', diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Source.py b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py index 2bc4b4dc2..332d0c488 100644 --- a/src/lib/Bcfg2/Server/Plugins/Packages/Source.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py @@ -1,7 +1,6 @@ import os import re import sys -import base64 import Bcfg2.Server.Plugin from Bcfg2.Bcfg2Py3k import HTTPError, HTTPBasicAuthHandler, \ HTTPPasswordMgrWithDefaultRealm, install_opener, build_opener, \ @@ -51,7 +50,18 @@ class Source(Bcfg2.Server.Plugin.Debuggable): for key, tag in [('components', 'Component'), ('arches', 'Arch'), ('blacklist', 'Blacklist'), ('whitelist', 'Whitelist')]: - self.__dict__[key] = [item.text for item in xsource.findall(tag)] + setattr(self, key, [item.text for item in xsource.findall(tag)]) + self.server_options = dict() + self.client_options = dict() + opts = xsource.findall("Options") + for el in opts: + repoopts = dict([(k, v) + for k, v in el.attrib.items() + if k != "clientonly" and k != "serveronly"]) + if el.get("clientonly", "false").lower() == "false": + self.server_options.update(repoopts) + if el.get("serveronly", "false").lower() == "false": + self.client_options.update(repoopts) self.gpgkeys = [el.text for el in xsource.findall("GPGKey")] @@ -149,12 +159,10 @@ class Source(Bcfg2.Server.Plugin.Debuggable): if match: name = match.group(1) break - if name is None: - # couldn't figure out the name from the URL or URL map - # (which probably means its a screwy URL), so we just - # generate a random one - name = base64.b64encode(os.urandom(16))[:-2] - rname = "%s-%s" % (self.groups[0], name) + if name is not None: + rname = "%s-%s" % (self.groups[0], name) + else: + rname = self.groups[0] # see yum/__init__.py in the yum source, lines 441-449, for # the source of this regex. yum doesn't like anything but # string.ascii_letters, string.digits, and [-_.:]. There @@ -185,6 +193,10 @@ class Source(Bcfg2.Server.Plugin.Debuggable): if a in metadata.groups] vdict = dict() for agrp in agroups: + if agrp not in self.provides: + self.logger.warning("%s provides no packages for %s" % + (self, agrp)) + continue for key, value in list(self.provides[agrp].items()): if key not in vdict: vdict[key] = set(value) diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py index ddc1aa0f9..87909dc4c 100644 --- a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py @@ -4,14 +4,13 @@ import time import copy import glob import socket -import random import logging import threading import lxml.etree from UserDict import DictMixin from subprocess import Popen, PIPE, STDOUT import Bcfg2.Server.Plugin -from Bcfg2.Bcfg2Py3k import StringIO, cPickle, HTTPError, ConfigParser, file +from Bcfg2.Bcfg2Py3k import StringIO, cPickle, HTTPError, URLError, ConfigParser, file from Bcfg2.Server.Plugins.Packages.Collection import Collection from Bcfg2.Server.Plugins.Packages.Source import SourceInitError, Source, \ fetch_url @@ -200,6 +199,13 @@ class YumCollection(Collection): config.set(reponame, "includepkgs", " ".join(source.whitelist)) + if raw: + opts = source.server_options + else: + opts = source.client_options + for opt, val in opts.items(): + config.set(reponame, opt, val) + if raw: return config else: @@ -560,6 +566,11 @@ class YumSource(Source): except ValueError: self.logger.error("Packages: Bad url string %s" % rmdurl) return [] + except URLError: + err = sys.exc_info()[1] + self.logger.error("Packages: Failed to fetch url %s. %s" % + (rmdurl, err)) + return [] except HTTPError: err = sys.exc_info()[1] self.logger.error("Packages: Failed to fetch url %s. code=%s" % diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py index 228bbfeb8..765d1c89d 100644 --- a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py @@ -92,8 +92,8 @@ class Packages(Bcfg2.Server.Plugin.Plugin, if entry.tag == 'Package': collection = self._get_collection(metadata) entry.set('version', self.core.setup.cfp.get("packages", - "version", - default="auto")) + "version", + default="auto")) entry.set('type', collection.ptype) elif entry.tag == 'Path': if (entry.get("name") == self.core.setup.cfp.get("packages", diff --git a/src/lib/Bcfg2/Server/Plugins/Probes.py b/src/lib/Bcfg2/Server/Plugins/Probes.py index 042146aa7..8ef6c8737 100644 --- a/src/lib/Bcfg2/Server/Plugins/Probes.py +++ b/src/lib/Bcfg2/Server/Plugins/Probes.py @@ -41,56 +41,25 @@ class ClientProbeDataSet(dict): dict.__init__(self, *args, **kwargs) -class ProbeData(object): +class ProbeData(str): """ a ProbeData object emulates a str object, but also has .xdata and .json properties to provide convenient ways to use ProbeData objects as XML or JSON data """ + def __new__(cls, data): + return str.__new__(cls, data) + def __init__(self, data): - self.data = data + str.__init__(self) self._xdata = None self._json = None self._yaml = None - def __str__(self): - return str(self.data) - - def __repr__(self): - return repr(self.data) - - def __getattr__(self, name): - """ make ProbeData act like a str object """ - return getattr(self.data, name) - - def __complex__(self): - return complex(self.data) - - def __int__(self): - return int(self.data) - - def __long__(self): - return long(self.data) - - def __float__(self): - return float(self.data) - - def __eq__(self, other): - return str(self) == str(other) - - def __ne__(self, other): - return str(self) != str(other) - - def __gt__(self, other): - return str(self) > str(other) - - def __lt__(self, other): - return str(self) < str(other) - - def __ge__(self, other): - return self > other or self == other - - def __le__(self, other): - return self < other or self == other - + @property + def data(self): + """ provide backwards compatibility with broken ProbeData + object in bcfg2 1.2.0 thru 1.2.2 """ + return str(self) + @property def xdata(self): if self._xdata is None: diff --git a/src/lib/Bcfg2/Server/Plugins/SSLCA.py b/src/lib/Bcfg2/Server/Plugins/SSLCA.py index 1091fc2c8..d207c45a2 100644 --- a/src/lib/Bcfg2/Server/Plugins/SSLCA.py +++ b/src/lib/Bcfg2/Server/Plugins/SSLCA.py @@ -3,12 +3,15 @@ import Bcfg2.Options import lxml.etree import posixpath import tempfile -import pipes import os from subprocess import Popen, PIPE, STDOUT # Compatibility import from Bcfg2.Bcfg2Py3k import ConfigParser +try: + from hashlib import md5 +except ImportError: + from md5 import md5 class SSLCA(Bcfg2.Server.Plugin.GroupSpool): """ @@ -22,6 +25,10 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): cert_specs = {} CAs = {} + def __init__(self, core, datastore): + Bcfg2.Server.Plugin.GroupSpool.__init__(self, core, datastore) + self.infoxml = dict() + def HandleEvent(self, event=None): """ Updates which files this plugin handles based upon filesystem events. @@ -37,7 +44,7 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): else: ident = self.handles[event.requestID][:-1] - fname = "".join([ident, '/', event.filename]) + fname = os.path.join(ident, event.filename) if event.filename.endswith('.xml'): if action in ['exists', 'created', 'changed']: @@ -69,6 +76,10 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): cp.read(self.core.cfile) self.CAs[ca] = dict(cp.items('sslca_' + ca)) self.Entries['Path'][ident] = self.get_cert + elif event.filename.endswith("info.xml"): + self.infoxml[ident] = Bcfg2.Server.Plugin.InfoXML(epath, + noprio=True) + self.infoxml[ident].HandleEvent(event) if action == 'deleted': if ident in self.Entries['Path']: del self.Entries['Path'][ident] @@ -92,28 +103,27 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): either grabs a prexisting key hostfile, or triggers the generation of a new key if one doesn't exist. """ - # set path type and permissions, otherwise bcfg2 won't bind the file - permdata = {'owner': 'root', - 'group': 'root', - 'type': 'file', - 'perms': '644'} - [entry.attrib.__setitem__(key, permdata[key]) for key in permdata] - # check if we already have a hostfile, or need to generate a new key # TODO: verify key fits the specs path = entry.get('name') - filename = "".join([path, '/', path.rsplit('/', 1)[1], - '.H_', metadata.hostname]) + filename = os.path.join(path, "%s.H_%s" % (os.path.basename(path), + metadata.hostname)) if filename not in list(self.entries.keys()): key = self.build_key(filename, entry, metadata) open(self.data + filename, 'w').write(key) entry.text = key - self.entries[filename] = self.__child__("%s%s" % (self.data, - filename)) + self.entries[filename] = self.__child__(self.data + filename) self.entries[filename].HandleEvent() else: entry.text = self.entries[filename].data + entry.set("type", "file") + if path in self.infoxml: + Bcfg2.Server.Plugin.bind_info(entry, metadata, + infoxml=self.infoxml[path]) + else: + Bcfg2.Server.Plugin.bind_info(entry, metadata) + def build_key(self, filename, entry, metadata): """ generates a new key according the the specification @@ -132,56 +142,61 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): either grabs a prexisting cert hostfile, or triggers the generation of a new cert if one doesn't exist. """ - # set path type and permissions, otherwise bcfg2 won't bind the file - permdata = {'owner': 'root', - 'group': 'root', - 'type': 'file', - 'perms': '644'} - [entry.attrib.__setitem__(key, permdata[key]) for key in permdata] - path = entry.get('name') - filename = "".join([path, '/', path.rsplit('/', 1)[1], - '.H_', metadata.hostname]) + filename = os.path.join(path, "%s.H_%s" % (os.path.basename(path), + metadata.hostname)) # first - ensure we have a key to work with key = self.cert_specs[entry.get('name')].get('key') - key_filename = "".join([key, '/', key.rsplit('/', 1)[1], - '.H_', metadata.hostname]) + key_filename = os.path.join(key, "%s.H_%s" % (os.path.basename(key), + metadata.hostname)) if key_filename not in self.entries: e = lxml.etree.Element('Path') - e.attrib['name'] = key + e.set('name', key) self.core.Bind(e, metadata) # check if we have a valid hostfile - if filename in list(self.entries.keys()) and self.verify_cert(filename, - key_filename, - entry): + if (filename in list(self.entries.keys()) and + self.verify_cert(filename, key_filename, entry)): entry.text = self.entries[filename].data else: cert = self.build_cert(key_filename, entry, metadata) open(self.data + filename, 'w').write(cert) - self.entries[filename] = self.__child__("%s%s" % (self.data, - filename)) + self.entries[filename] = self.__child__(self.data + filename) self.entries[filename].HandleEvent() entry.text = cert + entry.set("type", "file") + if path in self.infoxml: + Bcfg2.Server.Plugin.bind_info(entry, metadata, + infoxml=self.infoxml[path]) + else: + Bcfg2.Server.Plugin.bind_info(entry, metadata) + def verify_cert(self, filename, key_filename, entry): - if self.verify_cert_against_ca(filename, entry): - if self.verify_cert_against_key(filename, key_filename): - return True - return False + do_verify = self.CAs[self.cert_specs[entry.get('name')]['ca']].get('verify_certs', True) + if do_verify: + return (self.verify_cert_against_ca(filename, entry) and + self.verify_cert_against_key(filename, key_filename)) + return True def verify_cert_against_ca(self, filename, entry): """ check that a certificate validates against the ca cert, and that it has not expired. """ - chaincert = self.CAs[self.cert_specs[entry.get('name')]['ca']].get('chaincert') + chaincert = \ + self.CAs[self.cert_specs[entry.get('name')]['ca']].get('chaincert') cert = self.data + filename - res = Popen(["openssl", "verify", "-CAfile", chaincert, cert], + res = Popen(["openssl", "verify", "-untrusted", chaincert, "-purpose", + "sslserver", cert], stdout=PIPE, stderr=STDOUT).stdout.read() if res == cert + ": OK\n": + self.debug_log("SSLCA: %s verified successfully against CA" % + entry.get("name")) return True + self.logger.warning("SSLCA: %s failed verification against CA: %s" % + (entry.get("name"), res)) return False def verify_cert_against_key(self, filename, key_filename): @@ -190,14 +205,20 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): """ cert = self.data + filename key = self.data + key_filename - cmd = ("openssl x509 -noout -modulus -in %s | openssl md5" % - pipes.quote(cert)) - cert_md5 = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT).stdout.read() - cmd = ("openssl rsa -noout -modulus -in %s | openssl md5" % - pipes.quote(key)) - key_md5 = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT).stdout.read() + cert_md5 = \ + md5(Popen(["openssl", "x509", "-noout", "-modulus", "-in", cert], + stdout=PIPE, + stderr=STDOUT).stdout.read().strip()).hexdigest() + key_md5 = \ + md5(Popen(["openssl", "rsa", "-noout", "-modulus", "-in", key], + stdout=PIPE, + stderr=STDOUT).stdout.read().strip()).hexdigest() if cert_md5 == key_md5: + self.debug_log("SSLCA: %s verified successfully against key %s" % + (filename, key_filename)) return True + self.logger.warning("SSLCA: %s failed verification against key %s" % + (filename, key_filename)) return False def build_cert(self, key_filename, entry, metadata): diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info index 5303aa02b..617584d3d 100755 --- a/src/sbin/bcfg2-info +++ b/src/sbin/bcfg2-info @@ -597,6 +597,9 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): print(" %s" % "\n ".join(unknown)) def do_packagesources(self, args): + if not args: + print("Usage: packagesources <hostname>") + return try: metadata = self.build_metadata(args) except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError: |