mirror of
https://github.com/munki/munki.git
synced 2026-01-25 08:29:22 -06:00
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:
@@ -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__':
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user