mirror of
https://github.com/munki/munki.git
synced 2026-04-22 04:28:21 -05:00
More transition from plistlib to Foundation calls for plist handling.
Brian Warsing added support for cert-based authentication to secure webservers. Yay! git-svn-id: http://munki.googlecode.com/svn/trunk@170 a4e17f2e-e282-11dd-95e1-755cbddbdd66
This commit is contained in:
@@ -35,6 +35,8 @@ from xml.dom import minidom
|
||||
|
||||
#from SystemConfiguration import SCDynamicStoreCopyConsoleUser
|
||||
from Foundation import NSDictionary, NSDate
|
||||
from Foundation import NSData, NSPropertyListSerialization, NSPropertyListMutableContainers, NSPropertyListXMLFormat_v1_0
|
||||
from PyObjCTools import Conversion
|
||||
|
||||
import munkistatus
|
||||
|
||||
@@ -195,9 +197,15 @@ def readPlist(plistfile):
|
||||
Returns:
|
||||
dict of plist contents.
|
||||
"""
|
||||
return NSDictionary.dictionaryWithContentsOfFile_(plistfile)
|
||||
|
||||
|
||||
plistData = NSData.dataWithContentsOfFile_(plistfile)
|
||||
dataObject, plistFormat, error = NSPropertyListSerialization.propertyListFromData_mutabilityOption_format_errorDescription_(plistData, NSPropertyListMutableContainers, None, None)
|
||||
if not error:
|
||||
# convert from Obj-C collection to Python collection
|
||||
return Conversion.pythonCollectionFromPropertyList(dataObject)
|
||||
else:
|
||||
raise Exception(error)
|
||||
|
||||
|
||||
def getconsoleuser():
|
||||
# workaround no longer needed, but leaving this here for now...
|
||||
#osvers = int(os.uname()[2].split('.')[0])
|
||||
@@ -299,6 +307,8 @@ def getManagedInstallsPrefs():
|
||||
prefs['SoftwareUpdateServerURL'] = None
|
||||
prefs['DaysBetweenNotifications'] = 1
|
||||
prefs['LastNotifiedDate'] = '1970-01-01 00:00:00 -0000'
|
||||
# Added by bcw
|
||||
prefs['UseClientCertificate'] = False
|
||||
|
||||
prefsfile = "/Library/Preferences/ManagedInstalls.plist"
|
||||
pl = {}
|
||||
@@ -317,6 +327,12 @@ def getManagedInstallsPrefs():
|
||||
return prefs
|
||||
|
||||
|
||||
# Added by bcw
|
||||
def UseClientCertificate():
|
||||
prefs = getManagedInstallsPrefs()
|
||||
return prefs['UseClientCertificate']
|
||||
|
||||
|
||||
def ManagedInstallDir():
|
||||
prefs = getManagedInstallsPrefs()
|
||||
return prefs['ManagedInstallDir']
|
||||
@@ -496,7 +512,6 @@ def getOnePackageInfo(pkgpath):
|
||||
if os.path.exists(plistpath):
|
||||
pkginfo['filename'] = os.path.basename(pkgpath)
|
||||
pl = readPlist(plistpath)
|
||||
|
||||
if "CFBundleIdentifier" in pl:
|
||||
pkginfo['packageid'] = pl["CFBundleIdentifier"]
|
||||
elif "Bundle identifier" in pl:
|
||||
@@ -509,9 +524,11 @@ def getOnePackageInfo(pkgpath):
|
||||
pkginfo['name'] = pl["CFBundleName"]
|
||||
|
||||
if "IFPkgFlagInstalledSize" in pl:
|
||||
pkginfo['installed_size'] = pl["IFPkgFlagInstalledSize"]
|
||||
# converting to int because plistlib barfs on longs. This might be a problem...
|
||||
pkginfo['installed_size'] = int(pl["IFPkgFlagInstalledSize"])
|
||||
#pkginfo['installed_size'] = pl["IFPkgFlagInstalledSize"]
|
||||
|
||||
pkginfo['version'] = getExtendedVersion(pkgpath)
|
||||
pkginfo['version'] = getExtendedVersion(pkgpath)
|
||||
return pkginfo
|
||||
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import subprocess
|
||||
from distutils import version
|
||||
import urllib2
|
||||
import urlparse
|
||||
import httplib
|
||||
import hashlib
|
||||
import datetime
|
||||
import time
|
||||
@@ -40,7 +41,6 @@ import socket
|
||||
import munkicommon
|
||||
import munkistatus
|
||||
|
||||
|
||||
def reporterrors():
|
||||
# just a placeholder right now;
|
||||
# this needs to be expanded to support error reporting
|
||||
@@ -1433,10 +1433,57 @@ def checkServer():
|
||||
# httpDownload/getfilefromhttpurl/getHTTPfileIfNewerAtomically
|
||||
#
|
||||
|
||||
# urllib2 has no handler for client certificates, so make one...
|
||||
# Subclass HTTPSClientAuthHandler adapted from the following sources:
|
||||
# http://www.osmonov.com/2009/04/client-certificates-with-urllib2.html
|
||||
# http://www.threepillarsoftware.com/soap_client_auth
|
||||
# http://bugs.python.org/issue3466
|
||||
# bcw
|
||||
class HTTPSClientAuthHandler(urllib2.HTTPSHandler):
|
||||
def __init__(self, key, cert):
|
||||
urllib2.HTTPSHandler.__init__(self)
|
||||
self.key = key
|
||||
self.cert = cert
|
||||
def https_open(self, req):
|
||||
# Rather than pass in a reference to a connection class, we pass in
|
||||
# a reference to a function which, for all intents and purposes,
|
||||
# will behave as a constructor
|
||||
return self.do_open(self.getConnection, req)
|
||||
def getConnection(self, host, timeout=300):
|
||||
return httplib.HTTPSConnection(host, key_file=self.key, cert_file=self.cert)
|
||||
|
||||
# An empty subclass for identifying missing certs, bcw
|
||||
# Maybe there is a better place for this?
|
||||
class UseClientCertificateError(IOError):
|
||||
pass
|
||||
|
||||
def httpDownload(url, filename, headers={}, postData=None, reporthook=None, message=None):
|
||||
|
||||
# The required name for combination certifcate and private key
|
||||
# File must be PEM formatted and include the client's private key
|
||||
# bcw
|
||||
pemfile = 'munki.pem'
|
||||
|
||||
# Grab the prefs for UseClientCertificate and construct a loc for the cert, bcw
|
||||
ManagedInstallDir = munkicommon.ManagedInstallDir()
|
||||
UseClientCertificate = munkicommon.UseClientCertificate()
|
||||
cert = os.path.join(ManagedInstallDir, 'certs', pemfile)
|
||||
|
||||
reqObj = urllib2.Request(url, postData, headers)
|
||||
fp = urllib2.urlopen(reqObj)
|
||||
|
||||
if UseClientCertificate == True:
|
||||
# Check for the existence of the PEM file, bcw
|
||||
if os.path.isfile(cert):
|
||||
# Construct a secure urllib2 opener, bcw
|
||||
secureopener = urllib2.build_opener(HTTPSClientAuthHandler(cert, cert))
|
||||
fp = secureopener.open(reqObj)
|
||||
else:
|
||||
# No x509 cert so fail -0x509 (decimal -1289). So amusing. bcw
|
||||
raise UseClientCertificateError(-1289, "PEM file missing, %s" % cert)
|
||||
|
||||
else:
|
||||
fp = urllib2.urlopen(reqObj)
|
||||
|
||||
headers = fp.info()
|
||||
|
||||
if message:
|
||||
@@ -1480,7 +1527,6 @@ def httpDownload(url, filename, headers={}, postData=None, reporthook=None, mess
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def getfilefromhttpurl(url,filepath, ifmodifiedsince=None, message=None):
|
||||
"""
|
||||
gets a file from a url.
|
||||
@@ -1509,8 +1555,14 @@ def getfilefromhttpurl(url,filepath, ifmodifiedsince=None, message=None):
|
||||
|
||||
except urllib2.HTTPError, err:
|
||||
return err.code
|
||||
#except urllib2.URLError, err:
|
||||
# return err
|
||||
# Uncommented the exception handler below and added str(err)
|
||||
# This will catch missing or invalid certs/keys in getHTTPfileIfNewerAtomically
|
||||
# bcw
|
||||
except urllib2.URLError, err:
|
||||
return str(err)
|
||||
# This will catch missing certs in getHTTPfileIfNewerAtomically, bcw
|
||||
except UseClientCertificateError, err:
|
||||
return err
|
||||
except IOError, err:
|
||||
return err
|
||||
except Exception, err:
|
||||
@@ -1542,6 +1594,14 @@ def getHTTPfileIfNewerAtomically(url,destinationpath, message=None):
|
||||
# not modified, return existing file
|
||||
munkicommon.display_debug1("%s already exists and is up-to-date." % destinationpath)
|
||||
return destinationpath, err
|
||||
# Added to catch private key errors when the opener is constructed, bcw
|
||||
elif result == '<urlopen error SSL_CTX_use_PrivateKey_file error>':
|
||||
err = "SSL_CTX_use_PrivateKey_file error: PrivateKey Invalid or Missing"
|
||||
destinationpath = None
|
||||
# Added to catch certificate errors when the opener is constructed, bcw
|
||||
elif result == '<urlopen error SSL_CTX_use_certificate_chain_file error>':
|
||||
err = "SSL_CTX_use_certificate_chain_file error: Certificate Invalid or Missing"
|
||||
destinationpath = None
|
||||
else:
|
||||
err = "Error code: %s retreiving %s" % (result, url)
|
||||
destinationpath = None
|
||||
@@ -1551,6 +1611,7 @@ def getHTTPfileIfNewerAtomically(url,destinationpath, message=None):
|
||||
|
||||
return destinationpath, err
|
||||
|
||||
|
||||
def getMachineFacts():
|
||||
global machine
|
||||
|
||||
@@ -1688,5 +1749,5 @@ def main():
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user