summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/development/client-driver.txt2
-rw-r--r--doc/server/plugins/generators/packages.txt38
-rw-r--r--doc/server/plugins/generators/sslca.txt8
-rw-r--r--doc/server/plugins/probes/index.txt2
-rw-r--r--schemas/packages.xsd7
-rw-r--r--solaris/Makefile5
-rw-r--r--solaris/gen-prototypes.sh2
-rw-r--r--src/lib/Bcfg2/Bcfg2Py3k.py2
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX.py48
-rw-r--r--src/lib/Bcfg2/Server/Core.py8
-rw-r--r--src/lib/Bcfg2/Server/Plugin.py47
-rw-r--r--src/lib/Bcfg2/Server/Plugins/NagiosGen.py7
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Source.py28
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Yum.py15
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/__init__.py4
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Probes.py53
-rw-r--r--src/lib/Bcfg2/Server/Plugins/SSLCA.py105
-rwxr-xr-xsrc/sbin/bcfg2-info3
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: