Move more code from makekeychain to munkilib.keychain; attempt to build keychain on demand; should not need to run makekeychain at all

This commit is contained in:
Greg Neagle
2014-10-22 11:59:32 -07:00
parent fa22d8aad7
commit 38ebbf79ec
3 changed files with 409 additions and 342 deletions

View File

@@ -25,251 +25,10 @@ Incorporating work and ideas from Michael Lynn here:
import optparse
import os
import re
import sys
from munkilib import keychain
from munkilib import munkicommon
DEFAULT_KEYCHAIN_PASSWORD = 'munki'
def read_file(pathname):
'''Return the contents of pathname as a string'''
try:
fileobj = open(pathname, mode='r')
data = fileobj.read()
fileobj.close()
return data
except (OSError, IOError), err:
munkicommon.display_error(
'Could not read %s: %s' % (pathname, err))
return ''
def write_file(stringdata, pathname):
'''Writes stringdata to pathname.
Returns the pathname on success, empty string on failure.'''
try:
fileobject = open(pathname, mode='w')
fileobject.write(stringdata)
fileobject.close()
return pathname
except (OSError, IOError), err:
munkicommon.display_error("Couldn't write %s to %s: %s"
% (stringdata, pathname, err))
return ""
def add_ca_certs_to_system_keychain(ca_cert_path=None, ca_dir_path=None):
'''Adds any CA certs as trusted root certs to System.keychain'''
SYSTEM_KEYCHAIN = "/Library/Keychains/System.keychain"
if not os.path.exists(SYSTEM_KEYCHAIN):
munkicommon.display_info('%s not found.' % SYSTEM_KEYCHAIN)
return
if not ca_cert_path and not ca_dir_path:
# no CA certs, so nothing to do
munkicommon.display_info(
'No CA cert info provided, so nothing to add to System keychain.')
return
else:
munkicommon.display_info('CA cert path: %s', ca_cert_path)
munkicommon.display_info('CA dir path: %s', ca_dir_path)
# Add CA certs
if ca_cert_path:
munkicommon.display_info('Adding CA cert...')
output = keychain.security(
'add-trusted-cert', '-d', '-k', SYSTEM_KEYCHAIN, ca_cert_path)
if ca_dir_path:
# import any pem files in the ca_dir_path directory
for item in os.listdir(ca_dir_path):
if item.endswith('.pem'):
cert_path = os.path.join(ca_dir_path, item)
munkicommon.display_info('Adding CA cert %s...' % cert_path)
output = keychain.security('add-trusted-cert', '-d',
'-k', SYSTEM_KEYCHAIN, cert_path)
munkicommon.display_info('System.keychain updated.')
def make_client_keychain(client_cert_path=None, client_key_path=None,
site_url=None):
'''Builds a client cert keychain for use by managedsoftwareupdate'''
our_keychain = None
if not client_cert_path:
# no client, so nothing to do
munkicommon.display_info(
'No client cert info provided, '
'so no client keychain will be created.')
return
else:
munkicommon.display_info('Client cert path: %s', client_cert_path)
munkicommon.display_info('Client key path: %s', client_key_path)
keychain_pass = (
munkicommon.pref('KeychainPassword') or DEFAULT_KEYCHAIN_PASSWORD)
abs_keychain_path = keychain.get_keychain_path()
if os.path.exists(abs_keychain_path):
os.unlink(abs_keychain_path)
if not os.path.exists(os.path.dirname(abs_keychain_path)):
os.makedirs(os.path.dirname(abs_keychain_path))
# create a new keychain
munkicommon.display_info('Creating client keychain...')
output = keychain.security(
'create-keychain', '-p', keychain_pass, abs_keychain_path)
# Ensure the keychain is in the search path and unlocked
our_keychain = keychain.MunkiKeychain()
# Add client cert (and optionally key)
if client_cert_path:
if client_key_path:
# combine client cert and private key before we import
cert_data = read_file(client_cert_path)
key_data = read_file(client_key_path)
# write the combined data
combined_pem = os.path.join('/tmp', 'combined.pem')
if write_file(cert_data + key_data, combined_pem):
munkicommon.display_info('Importing client cert and key...')
output = keychain.security(
'import', combined_pem, '-A', '-k', abs_keychain_path)
os.unlink(combined_pem)
else:
munkicommon.display_info('Importing client cert and key...')
output = keychain.security(
'import', client_cert_path, '-A', '-k', abs_keychain_path)
# set up identity preference linking the identity (cert and key)
# to the site_url
if not site_url:
# trailing slash is needed so preference is used for all
# URLs at the site
site_url = munkicommon.pref('SoftwareRepoURL').rstrip('/') + '/'
# Set up an identity if it doesn't exist already for our site
# First we need to find the existing identity in our keychain
output = keychain.security('find-identity', abs_keychain_path)
if not ' 1 identities found' in output:
munkicommon.display_error('No identities found!')
else:
# We have a solitary match and can configure / verify
# the identity preference
id_hash = re.findall(r'\W+1\)\W+([0-9A-F]+)\W', output)[0]
# First, check to see if we have an identity already
create_identity = False
try:
output = keychain.security(
'get-identity-preference', '-s', site_url, '-Z')
# No error, we found an identity
# Check if it matches the one we want
current_hash = re.match(
r'SHA-1 hash:\W+([A-F0-9]+)\W', output).group(1)
if id_hash != current_hash:
# We only care if there's a different hash being used.
# Remove the incorrect one.
output = keychain.security(
'set-identity-preference', '-n', '-s', site_url)
# Signal that we want to create a new identity preference
create_identity = True
except keychain.SecurityError, err:
# error finding identity-preference
create_identity = True
#elif id_hash not in output:
# # Non-zero error code and hash not detected in output
# # Signal that we want to create a new identity preference
# create_identity = True
if create_identity:
# This code was moved into a common block that both routes could
# access as it's a little complicated.
# security will only create an identity preference in the
# default keychain - which means a default has to be
# defined/selected. For normal users, this is login.keychain -
# but for root there's no login.keychain and no default keychain
# configured. So we'll handle the case of no default keychain
# (just set one) as well as pre-existing default keychain
# (in which case we set it long enough to create the preference,
# then set it back)
munkicommon.display_info('Creating identity preference...')
try:
output = keychain.security('default-keychain')
# One is defined, remember the path
default_keychain = [
x.strip().strip('"')
for x in output.split('\n') if x.strip()][0]
except keychain.SecurityError, err:
# error raised if there is no default
default_keychain = None
# Temporarily assign the default keychain to ours
try:
output = keychain.security(
'default-keychain', '-s', abs_keychain_path)
except keychain.SecurityError, err:
munkicommon.display_error(
'Could not set default keychain to %s failed: %s'
% (abs_keychain_path, err))
default_keychain = None
# Create the identity preference
try:
output = keychain.security(
'set-identity-preference', '-s', site_url, '-Z',
id_hash, abs_keychain_path)
except keychain.SecurityError, err:
munkicommon.display_error(
'Setting identity preference failed: %s' % err)
if default_keychain:
# We originally had a different one, set it back
output = keychain.security(
'default-keychain', '-s', default_keychain)
if our_keychain:
# remove it from the keychain list
our_keychain.remove_from_search_list()
munkicommon.display_info(
'Completed creation of client keychain at %s' % abs_keychain_path)
def get_munki_cert_data():
'''Attempt to get information we need from Munki's preferences or
defaults.'''
ManagedInstallDir = munkicommon.pref('ManagedInstallDir')
cert_data = {}
# get server CA cert if it exists so we can verify the munki server
cert_data['ca_cert_path'] = None
cert_data['ca_dir_path'] = None
if munkicommon.pref('SoftwareRepoCAPath'):
CA_path = munkicommon.pref('SoftwareRepoCAPath')
if os.path.isfile(CA_path):
cert_data['ca_cert_path'] = CA_path
elif os.path.isdir(CA_path):
cert_data['ca_dir_path'] = CA_path
if munkicommon.pref('SoftwareRepoCACertificate'):
cert_data['ca_cert_path'] = munkicommon.pref(
'SoftwareRepoCACertificate')
if cert_data['ca_cert_path'] == None:
ca_cert_path = os.path.join(ManagedInstallDir, 'certs', 'ca.pem')
if os.path.exists(ca_cert_path):
cert_data['ca_cert_path'] = ca_cert_path
cert_data['client_cert_path'] = None
cert_data['client_key_path'] = None
# get client cert if it exists
if munkicommon.pref('UseClientCertificate'):
cert_data['client_cert_path'] = (
munkicommon.pref('ClientCertificatePath') or None)
cert_data['client_key_path'] = munkicommon.pref('ClientKeyPath') or None
if not cert_data['client_cert_path']:
for name in ['cert.pem', 'client.pem', 'munki.pem']:
client_cert_path = os.path.join(
ManagedInstallDir, 'certs', name)
if os.path.exists(client_cert_path):
cert_data['client_cert_path'] = client_cert_path
break
return cert_data
from munkilib import keychain
def main():
@@ -290,40 +49,29 @@ def main():
print >> sys.stderr, 'You must run this as root!'
exit(munkicommon.EXIT_STATUS_ROOT_REQUIRED)
if os.environ['HOME'] != '/var/root':
print >> sys.stderr, (
'SORRY! '
'You must actually login as root, or use `sudo su` to inherit '
'root\'s environment before running this tool. '
'`sudo` or `sudo -s` is not sufficient.')
exit(munkicommon.EXIT_STATUS_ROOT_REQUIRED)
if arguments:
print >> sys.stderr, 'Extra arguments supplied!'
parser.print_usage()
exit(-1)
cert_data = get_munki_cert_data()
server_cert_data = keychain.get_munki_server_cert_data()
client_cert_data = keychain.get_munki_client_cert_data()
# command-line options override what we find from Munki
if options.ca_cert_path:
cert_data['ca_cert_path'] = options.ca_cert_path
server_cert_data['ca_cert_path'] = options.ca_cert_path
if options.ca_dir_path:
cert_data['ca_dir_path'] = options.ca_dir_path
server_cert_data['ca_dir_path'] = options.ca_dir_path
if options.client_cert_path:
cert_data['client_cert_path'] = options.client_cert_path
client_cert_data['client_cert_path'] = options.client_cert_path
if options.client_key_path:
cert_data['client_key_path'] = options.client_key_path
site_url = None
client_cert_data['client_key_path'] = options.client_key_path
if options.site_url:
site_url = options.site_url
client_cert_data['site_url'] = options.site_url
add_ca_certs_to_system_keychain(ca_cert_path=cert_data['ca_cert_path'],
ca_dir_path=cert_data['ca_dir_path'])
make_client_keychain(client_cert_path=cert_data['client_cert_path'],
client_key_path=cert_data['client_key_path'],
site_url=site_url)
keychain.add_ca_certs_to_system_keychain(server_cert_data)
keychain.make_client_keychain(client_cert_data)
if __name__ == '__main__':

View File

@@ -189,7 +189,7 @@ def get_url(url, destinationpath,
connection.error.localizedDescription())
if connection.SSLerror:
munkicommon.display_detail(
'SSL error detail: %s' % str(connection.SSLerror))
'SSL error detail: %s', str(connection.SSLerror))
keychain.debug_output()
munkicommon.display_detail('Headers: %s', connection.headers)
if os.path.exists(tempdownloadpath) and not resume:
@@ -393,8 +393,8 @@ def getHTTPfileIfChangedAtomically(url, destinationpath,
err = None
if header['http_result_code'] == '304':
# not modified, return existing file
munkicommon.display_debug1('%s already exists and is up-to-date.'
% destinationpath)
munkicommon.display_debug1('%s already exists and is up-to-date.',
destinationpath)
# file is in cache and is unchanged, so we return False
return False
else:

View File

@@ -25,9 +25,7 @@ Incorporating work and ideas from Michael Lynn here:
import os
import re
import stat
import subprocess
import sys
import munkicommon
@@ -38,20 +36,390 @@ KEYCHAIN_DIRECTORY = os.path.join(
munkicommon.pref('ManagedInstallDir'), 'Keychains')
def read_file(pathname):
'''Return the contents of pathname as a string'''
try:
fileobj = open(pathname, mode='r')
data = fileobj.read()
fileobj.close()
return data
except (OSError, IOError), err:
munkicommon.display_error(
'Could not read %s: %s', pathname, err)
return ''
def write_file(stringdata, pathname):
'''Writes stringdata to pathname.
Returns the pathname on success, empty string on failure.'''
try:
fileobject = open(pathname, mode='w')
fileobject.write(stringdata)
fileobject.close()
return pathname
except (OSError, IOError), err:
munkicommon.display_error('Couldn\'t write %s to %s: %s',
stringdata, pathname, err)
return ''
def get_munki_server_cert_data():
'''Attempt to get information we need from Munki's preferences or
defaults. Returns a dictionary.'''
ManagedInstallDir = munkicommon.pref('ManagedInstallDir')
cert_data = {}
# get server CA cert if it exists so we can verify the Munki server
cert_data['ca_cert_path'] = None
cert_data['ca_dir_path'] = None
if munkicommon.pref('SoftwareRepoCAPath'):
CA_path = munkicommon.pref('SoftwareRepoCAPath')
if os.path.isfile(CA_path):
cert_data['ca_cert_path'] = CA_path
elif os.path.isdir(CA_path):
cert_data['ca_dir_path'] = CA_path
if munkicommon.pref('SoftwareRepoCACertificate'):
cert_data['ca_cert_path'] = munkicommon.pref(
'SoftwareRepoCACertificate')
if cert_data['ca_cert_path'] == None:
ca_cert_path = os.path.join(ManagedInstallDir, 'certs', 'ca.pem')
if os.path.exists(ca_cert_path):
cert_data['ca_cert_path'] = ca_cert_path
return cert_data
def get_munki_client_cert_data():
'''Attempt to get information we need from Munki's preferences or
defaults. Returns a dictionary.'''
ManagedInstallDir = munkicommon.pref('ManagedInstallDir')
cert_data = {}
cert_data['client_cert_path'] = None
cert_data['client_key_path'] = None
# get client cert if it exists
if munkicommon.pref('UseClientCertificate'):
cert_data['client_cert_path'] = (
munkicommon.pref('ClientCertificatePath') or None)
cert_data['client_key_path'] = munkicommon.pref('ClientKeyPath') or None
if not cert_data['client_cert_path']:
for name in ['cert.pem', 'client.pem', 'munki.pem']:
client_cert_path = os.path.join(
ManagedInstallDir, 'certs', name)
if os.path.exists(client_cert_path):
cert_data['client_cert_path'] = client_cert_path
break
cert_data['site_url'] = (
munkicommon.pref('SoftwareRepoURL').rstrip('/') + '/')
return cert_data
def add_ca_certs_to_system_keychain(certdata=None):
'''Adds any CA certs as trusted root certs to System.keychain'''
if not certdata:
certdata = get_munki_server_cert_data()
ca_cert_path = certdata['ca_cert_path']
ca_dir_path = certdata['ca_dir_path']
SYSTEM_KEYCHAIN = "/Library/Keychains/System.keychain"
if not os.path.exists(SYSTEM_KEYCHAIN):
munkicommon.display_warning('%s not found.', SYSTEM_KEYCHAIN)
return
if not ca_cert_path and not ca_dir_path:
# no CA certs, so nothing to do
munkicommon.display_debug2(
'No CA cert info provided, so nothing to add to System keychain.')
return
else:
munkicommon.display_debug2('CA cert path: %s', ca_cert_path)
munkicommon.display_debug2('CA dir path: %s', ca_dir_path)
# Add CA certs
certs_to_add = []
if ca_cert_path:
certs_to_add.append(ca_cert_path)
if ca_dir_path:
# add any pem files in the ca_dir_path directory
for item in os.listdir(ca_dir_path):
if item.endswith('.pem'):
certs_to_add.append(os.path.join(ca_dir_path, item))
for cert in certs_to_add:
munkicommon.display_debug1('Adding CA cert %s...', cert)
try:
output = security('add-trusted-cert', '-d',
'-k', SYSTEM_KEYCHAIN, cert)
munkicommon.display_debug2(output)
except SecurityError, err:
munkicommon.display_error(
'Could not add CA cert %s into System keychain: %s', cert, err)
munkicommon.display_info('System.keychain updated.')
def make_client_keychain(certdata=None):
'''Builds a client cert keychain from existing client certs'''
if not certdata:
# jusr grab data from Munki's preferences/defaults
certdata = get_munki_client_cert_data()
client_cert_path = certdata['client_cert_path']
client_key_path = certdata['client_key_path']
site_url = certdata['site_url']
if not client_cert_path:
# no client, so nothing to do
munkicommon.display_debug1(
'No client cert info provided, '
'so no client keychain will be created.')
return
else:
munkicommon.display_debug1('Client cert path: %s', client_cert_path)
munkicommon.display_debug1('Client key path: %s', client_key_path)
# to do some of the following options correctly, we need to be root
# and have root's home.
# check to see if we're root
if os.geteuid() != 0:
munkicommon.display_error(
'Can\'t make our client keychain unless we are root!')
return
# switch HOME if needed to root's home
original_home = os.environ.get('HOME')
if original_home:
os.environ['HOME'] = os.path.expanduser('~root')
keychain_pass = (
munkicommon.pref('KeychainPassword') or DEFAULT_KEYCHAIN_PASSWORD)
abs_keychain_path = get_keychain_path()
if os.path.exists(abs_keychain_path):
os.unlink(abs_keychain_path)
if not os.path.exists(os.path.dirname(abs_keychain_path)):
os.makedirs(os.path.dirname(abs_keychain_path))
# create a new keychain
munkicommon.display_debug1('Creating client keychain...')
try:
output = security('create-keychain',
'-p', keychain_pass, abs_keychain_path)
except SecurityError, err:
munkicommon.display_error(
'Could not create keychain %s: %s', abs_keychain_path, err)
if original_home:
# switch it back
os.environ['HOME'] = original_home
return
# Ensure the keychain is in the search path and unlocked
added_keychain = add_to_keychain_list(abs_keychain_path)
unlock_and_set_nonlocking(abs_keychain_path)
# Add client cert (and optionally key)
if client_key_path:
# combine client cert and private key before we import
cert_data = read_file(client_cert_path)
key_data = read_file(client_key_path)
# write the combined data
combined_pem = os.path.join(munkicommon.tmpdir, 'combined.pem')
if write_file(cert_data + key_data, combined_pem):
munkicommon.display_debug1('Importing client cert and key...')
try:
output = security(
'import', combined_pem, '-A', '-k', abs_keychain_path)
except SecurityError, err:
munkicommon.display_error(
'Could not import %s: %s', combined_pem, err)
os.unlink(combined_pem)
else:
munkicommon.display_error(
'Could not combine client cert and key for import!')
else:
munkicommon.display_debug2('Importing client cert and key...')
try:
output = security(
'import', client_cert_path, '-A', '-k', abs_keychain_path)
except SecurityError, err:
munkicommon.display_error(
'Could not import %s: %s', client_cert_path, err)
# set up identity preference linking the identity (cert and key)
# to the site_url
# First we need to find the existing identity in our keychain
try:
output = security('find-identity', abs_keychain_path)
except SecurityError:
pass
if not ' 1 identities found' in output:
munkicommon.display_error('No identities found!')
else:
# We have a solitary match and can configure / verify
# the identity preference
id_hash = re.findall(r'\W+1\)\W+([0-9A-F]+)\W', output)[0]
# First, check to see if we have an identity already
create_identity = False
try:
output = security(
'get-identity-preference', '-s', site_url, '-Z')
# No error, we found an identity
# Check if it matches the one we want
current_hash = re.match(
r'SHA-1 hash:\W+([A-F0-9]+)\W', output).group(1)
if id_hash != current_hash:
# We only care if there's a different hash being used.
# Remove the incorrect one.
output = security(
'set-identity-preference', '-n', '-s', site_url)
# Signal that we want to create a new identity preference
create_identity = True
except SecurityError, err:
# error finding identity-preference
create_identity = True
#elif id_hash not in output:
# # Non-zero error code and hash not detected in output
# # Signal that we want to create a new identity preference
# create_identity = True
if create_identity:
# This code was moved into a common block that both routes could
# access as it's a little complicated.
# security will only create an identity preference in the
# default keychain - which means a default has to be
# defined/selected. For normal users, this is login.keychain -
# but for root there's no login.keychain and no default keychain
# configured. So we'll handle the case of no default keychain
# (just set one) as well as pre-existing default keychain
# (in which case we set it long enough to create the preference,
# then set it back)
munkicommon.display_debug1('Creating identity preference...')
try:
output = security('default-keychain')
# One is defined, remember the path
default_keychain = [
x.strip().strip('"')
for x in output.split('\n') if x.strip()][0]
except SecurityError, err:
# error raised if there is no default
default_keychain = None
# Temporarily assign the default keychain to ours
try:
output = security(
'default-keychain', '-s', abs_keychain_path)
except SecurityError, err:
munkicommon.display_error(
'Could not set default keychain to %s failed: %s'
% (abs_keychain_path, err))
default_keychain = None
# Create the identity preference
try:
output = security(
'set-identity-preference', '-s', site_url, '-Z',
id_hash, abs_keychain_path)
except SecurityError, err:
munkicommon.display_error(
'Setting identity preference failed: %s' % err)
if default_keychain:
# We originally had a different one, set it back
output = security(
'default-keychain', '-s', default_keychain)
# we're done, clean up.
remove_from_keychain_list(abs_keychain_path)
if original_home:
# switch it back
os.environ['HOME'] = original_home
munkicommon.display_info(
'Completed creation of client keychain at %s' % abs_keychain_path)
def add_to_keychain_list(keychain_path):
'''Ensure the keychain is in the search path. Returns True if we
added the keychain to the list.'''
added_keychain = False
output = security('list-keychains', '-d', 'user')
# Split the output and strip it of whitespace and leading/trailing
# quotes, the result are absolute paths to keychains
# Preserve the order in case we need to append to them
search_keychains = [x.strip().strip('"')
for x in output.split('\n') if x.strip()]
if not keychain_path in search_keychains:
# Keychain is not in the search paths
munkicommon.display_debug2('Adding client keychain to search path...')
search_keychains.append(keychain_path)
try:
output = security(
'list-keychains', '-d', 'user', '-s', *search_keychains)
added_keychain = True
except SecurityError, err:
munkicommon.display_error(
'Could not add keychain %s to keychain list: %s',
keychain_path, err)
added_keychain = False
return added_keychain
def unlock_and_set_nonlocking(keychain_path):
'''Unlocks the keychain and sets it to non-locking'''
keychain_pass = (
munkicommon.pref('KeychainPassword') or DEFAULT_KEYCHAIN_PASSWORD)
try:
output = security(
'unlock-keychain', '-p', keychain_pass, keychain_path)
munkicommon.display_debug2(output)
except SecurityError, err:
# some problem unlocking the keychain.
munkicommon.display_error(
'Could not unlock %s: %s.', keychain_path, err)
# delete it
try:
os.unlink(keychain_path)
except OSError, err:
munkicommon.display_error(
'Could not remove %s: %s.', keychain_path, err)
return
try:
output = security('set-keychain-settings', keychain_path)
munkicommon.display_debug2(output)
except SecurityError, err:
munkicommon.display_error(
'Could not set keychain settings for %s: %s',
keychain_path, err)
def remove_from_keychain_list(keychain_path):
'''Remove keychain from the list of keychains'''
output = security('list-keychains', '-d', 'user')
# Split the output and strip it of whitespace and leading/trailing
# quotes, the result are absolute paths to keychains
# Preserve the order in case we need to append to them
search_keychains = [x.strip().strip('"')
for x in output.split('\n') if x.strip()]
if keychain_path in search_keychains:
# Keychain is in the search path
munkicommon.display_debug1(
'Removing %s from search path...', keychain_path)
filtered_keychains = [keychain for keychain in search_keychains
if keychain != keychain_path]
try:
output = security(
'list-keychains', '-d', 'user', '-s', *filtered_keychains)
except SecurityError, err:
munkicommon.display_error(
'Could not set new keychain list: %s', err)
def debug_output():
'''Debugging output for keychain'''
try:
munkicommon.display_info('***Keychain list***')
munkicommon.display_info(security('list-keychains', '-d', 'user'))
munkicommon.display_info('***Default keychain info***')
munkicommon.display_info(security('default-keychain', '-d', 'user'))
munkicommon.display_debug1('***Keychain list***')
munkicommon.display_debug1(security('list-keychains', '-d', 'user'))
munkicommon.display_debug1('***Default keychain info***')
munkicommon.display_debug1(security('default-keychain', '-d', 'user'))
keychainfile = get_keychain_path()
if os.path.exists(keychainfile):
munkicommon.display_info('***Info for %s***' % keychainfile)
munkicommon.display_info(
munkicommon.display_debug1('***Info for %s***' % keychainfile)
munkicommon.display_debug1(
security('show-keychain-info', keychainfile))
except SecurityError, err:
munkicommon.display_info(str(err))
munkicommon.display_error(str(err))
class SecurityError(Exception):
@@ -97,83 +465,34 @@ def get_keychain_path():
class MunkiKeychain(object):
'''Wrapper class for handling the client keychain'''
keychain_path = None
added_keychain = False
def __init__(self):
'''Unlocks the munki.keychain if it exists.
Makes sure the munki.keychain is in the search list.'''
'''Adds CA certs as trusted to System keychain.
Unlocks the munki.keychain if it exists.
Makes sure the munki.keychain is in the search list.
Creates a new client keychain if needed.'''
add_ca_certs_to_system_keychain()
self.keychain_path = get_keychain_path()
keychain_pass = (
munkicommon.pref('KeychainPassword') or DEFAULT_KEYCHAIN_PASSWORD)
if os.path.exists(self.keychain_path):
self.ensure_in_search_list()
try:
output = security(
'unlock-keychain', '-p', keychain_pass, self.keychain_path)
except SecurityError, err:
# some problem unlocking the keychain.
munkicommon.display_error(
'Could not unlock %s: %s.' % (self.keychain_path, err))
self.keychain_path = None
return
try:
output = security('set-keychain-settings', self.keychain_path)
except SecurityError, err:
munkicommon.display_error(
'Could not set keychain settings for %s: %s'
% (self.keychain_path, err))
# ensure existing keychain is available for use
self.added_keychain = add_to_keychain_list(self.keychain_path)
unlock_and_set_nonlocking(self.keychain_path)
if not os.path.exists(self.keychain_path):
# try making a new keychain
make_client_keychain()
if os.path.exists(self.keychain_path):
self.added_keychain = add_to_keychain_list(self.keychain_path)
unlock_and_set_nonlocking(self.keychain_path)
if not os.path.exists(self.keychain_path):
# give up
self.keychain_path = None
self.added_keychain = False
def __del__(self):
'''Remove our keychain from the keychain list if we added it'''
if self.added_keychain:
self.remove_from_search_list()
def ensure_in_search_list(self):
'''Ensure the keychain is in the search path.'''
self.added_keychain = False
output = security('list-keychains', '-d', 'user')
# Split the output and strip it of whitespace and leading/trailing
# quotes, the result are absolute paths to keychains
# Preserve the order in case we need to append to them
search_keychains = [x.strip().strip('"')
for x in output.split('\n') if x.strip()]
if not self.keychain_path in search_keychains:
# Keychain is not in the search paths
munkicommon.display_debug1('Adding keychain to search path...')
search_keychains.append(self.keychain_path)
try:
output = security(
'list-keychains', '-d', 'user', '-s', *search_keychains)
self.added_keychain = True
except SecurityError, err:
munkicommon.display_error(
'Could not add keychain %s to keychain list: %s'
% (self.keychain_path, err))
self.added_keychain = False
def remove_from_search_list(self):
'''Remove our keychain from the list of keychains'''
output = security('list-keychains', '-d', 'user')
# Split the output and strip it of whitespace and leading/trailing
# quotes, the result are absolute paths to keychains
# Preserve the order in case we need to append to them
search_keychains = [x.strip().strip('"')
for x in output.split('\n') if x.strip()]
if self.keychain_path in search_keychains:
# Keychain is in the search path
munkicommon.display_debug1(
'Removing %s from search path...' % self.keychain_path)
filtered_keychains = [keychain for keychain in search_keychains
if keychain != self.keychain_path]
try:
output = security(
'list-keychains', '-d', 'user', '-s', *filtered_keychains)
self.added_keychain = False
except SecurityError, err:
munkicommon.display_error(
'Could not set new keychain list: %s' % err)
remove_from_keychain_list(self.keychain_path)