Merge remote-tracking branch 'refs/remotes/chrismeyers/fix-cloud_credentials2' into upstream_master

* refs/remotes/chrismeyers/fix-cloud_credentials2:
  multiple credentials implementation style more inline with existing code
  allow machine and cloud credentials to co-exist.
This commit is contained in:
Matthew Jones
2015-04-23 16:09:14 -04:00

View File

@@ -230,10 +230,10 @@ class BaseTask(Task):
''' '''
return os.path.abspath(os.path.join(os.path.dirname(__file__), *args)) return os.path.abspath(os.path.join(os.path.dirname(__file__), *args))
def build_private_data(self, instance, **kwargs): def build_private_data(self, job, **kwargs):
''' '''
Return any private data that needs to be written to a temporary file Return SSH private key data (only if stored in DB as ssh_key_data).
for this task. Return structure is a dict of the form:
''' '''
def build_private_data_dir(self, instance, **kwargs): def build_private_data_dir(self, instance, **kwargs):
@@ -244,20 +244,23 @@ class BaseTask(Task):
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
return path return path
def build_private_data_file(self, instance, **kwargs): def build_private_data_files(self, instance, **kwargs):
''' '''
Create a temporary file containing the private data. Create a temporary files containing the private data.
Returns a dictionary with keys from build_private_data
(i.e. 'credential', 'cloud_credential') and values the file path.
''' '''
private_data = self.build_private_data(instance, **kwargs) private_data = self.build_private_data(instance, **kwargs)
private_data_files = {}
if private_data is not None: if private_data is not None:
handle, path = tempfile.mkstemp(dir=kwargs.get('private_data_dir', None)) for name, data in private_data.iteritems():
f = os.fdopen(handle, 'w') handle, path = tempfile.mkstemp(dir=kwargs.get('private_data_dir', None))
f.write(private_data) f = os.fdopen(handle, 'w')
f.close() f.write(data)
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR) f.close()
return path os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
else: private_data_files[name] = path
return '' return private_data_files
def build_passwords(self, instance, **kwargs): def build_passwords(self, instance, **kwargs):
''' '''
@@ -434,7 +437,8 @@ class BaseTask(Task):
# Fetch ansible version once here to support version-dependent features. # Fetch ansible version once here to support version-dependent features.
kwargs['ansible_version'] = get_ansible_version() kwargs['ansible_version'] = get_ansible_version()
kwargs['private_data_dir'] = self.build_private_data_dir(instance, **kwargs) kwargs['private_data_dir'] = self.build_private_data_dir(instance, **kwargs)
kwargs['private_data_file'] = self.build_private_data_file(instance, **kwargs) # May have to serialize the value
kwargs['private_data_files'] = self.build_private_data_files(instance, **kwargs)
kwargs['passwords'] = self.build_passwords(instance, **kwargs) kwargs['passwords'] = self.build_passwords(instance, **kwargs)
args = self.build_args(instance, **kwargs) args = self.build_args(instance, **kwargs)
safe_args = self.build_safe_args(instance, **kwargs) safe_args = self.build_safe_args(instance, **kwargs)
@@ -505,25 +509,22 @@ class RunJob(BaseTask):
def build_private_data(self, job, **kwargs): def build_private_data(self, job, **kwargs):
''' '''
Return SSH private key data needed for this job (only if stored in DB Returns a dict of the form
as ssh_key_data). dict['credential'] = <credential_decrypted_ssh_key_data>
dict['cloud_credential'] = <cloud_credential_decrypted_ssh_key_data>
''' '''
job_credentials = ['credential', 'cloud_credential']
private_data = {}
# If we were sent SSH credentials, decrypt them and send them # If we were sent SSH credentials, decrypt them and send them
# back (they will be written to a temporary file). # back (they will be written to a temporary file).
credential = getattr(job, 'credential', None)
if credential:
return decrypt_field(credential, 'ssh_key_data') or None
# We might also have been sent a cloud credential. If so, send it. for cred_name in job_credentials:
# credential = getattr(job, cred_name, None)
# This sets up an either/or situation with credential and cloud if credential:
# credential when it comes to SSH data. This should be fine, as if if credential.ssh_key_data not in (None, ''):
# you're running against cloud instances, you'll be using the cloud private_data[cred_name] = decrypt_field(credential, 'ssh_key_data') or ''
# credentials to do so. I assert that no situation currently exists
# where we need both. return private_data
cloud_credential = getattr(job, 'cloud_credential', None)
if cloud_credential:
return decrypt_field(cloud_credential, 'ssh_key_data') or None
def build_passwords(self, job, **kwargs): def build_passwords(self, job, **kwargs):
''' '''
@@ -583,10 +584,10 @@ class RunJob(BaseTask):
elif cloud_cred and cloud_cred.kind == 'gce': elif cloud_cred and cloud_cred.kind == 'gce':
env['GCE_EMAIL'] = cloud_cred.username env['GCE_EMAIL'] = cloud_cred.username
env['GCE_PROJECT'] = cloud_cred.project env['GCE_PROJECT'] = cloud_cred.project
env['GCE_PEM_FILE_PATH'] = kwargs['private_data_file'] env['GCE_PEM_FILE_PATH'] = kwargs.get('private_data_files', {}).get('cloud_credential', '')
elif cloud_cred and cloud_cred.kind == 'azure': elif cloud_cred and cloud_cred.kind == 'azure':
env['AZURE_SUBSCRIPTION_ID'] = cloud_cred.username env['AZURE_SUBSCRIPTION_ID'] = cloud_cred.username
env['AZURE_CERT_PATH'] = kwargs['private_data_file'] env['AZURE_CERT_PATH'] = kwargs.get('private_data_files', {}).get('cloud_credential', '')
elif cloud_cred and cloud_cred.kind == 'vmware': elif cloud_cred and cloud_cred.kind == 'vmware':
env['VMWARE_USER'] = cloud_cred.username env['VMWARE_USER'] = cloud_cred.username
env['VMWARE_PASSWORD'] = decrypt_field(cloud_cred, 'password') env['VMWARE_PASSWORD'] = decrypt_field(cloud_cred, 'password')
@@ -729,7 +730,7 @@ class RunJob(BaseTask):
''' '''
If using an SSH key, return the path for use by ssh-agent. If using an SSH key, return the path for use by ssh-agent.
''' '''
return kwargs.get('private_data_file', '') return kwargs.get('private_data_files', {}).get('credential', '')
def should_use_proot(self, instance, **kwargs): def should_use_proot(self, instance, **kwargs):
''' '''
@@ -760,12 +761,15 @@ class RunProjectUpdate(BaseTask):
name = 'awx.main.tasks.run_project_update' name = 'awx.main.tasks.run_project_update'
model = ProjectUpdate model = ProjectUpdate
def build_private_data(self, project_update, **kwargs): def build_private_data(self, project_update, **kwargs):
''' '''
Return SSH private key data needed for this project update. Return SSH private key data needed for this project update.
''' '''
private_data = {}
if project_update.credential: if project_update.credential:
return decrypt_field(project_update.credential, 'ssh_key_data') or None private_data['scm_credential'] = decrypt_field(project_update.credential, 'ssh_key_data') or None
return private_data
def build_passwords(self, project_update, **kwargs): def build_passwords(self, project_update, **kwargs):
''' '''
@@ -916,7 +920,7 @@ class RunProjectUpdate(BaseTask):
''' '''
If using an SSH key, return the path for use by ssh-agent. If using an SSH key, return the path for use by ssh-agent.
''' '''
return kwargs.get('private_data_file', '') return kwargs.get('private_data_files', {}).get('scm_credential', '')
class RunInventoryUpdate(BaseTask): class RunInventoryUpdate(BaseTask):
@@ -930,7 +934,7 @@ class RunInventoryUpdate(BaseTask):
# If this is Microsoft Azure or GCE, return the RSA key # If this is Microsoft Azure or GCE, return the RSA key
if inventory_update.source in ('azure', 'gce'): if inventory_update.source in ('azure', 'gce'):
credential = inventory_update.credential credential = inventory_update.credential
return decrypt_field(credential, 'ssh_key_data') return dict(scm_credential=decrypt_field(credential, 'ssh_key_data'))
if inventory_update.source == 'openstack': if inventory_update.source == 'openstack':
credential = inventory_update.credential credential = inventory_update.credential
@@ -994,7 +998,7 @@ class RunInventoryUpdate(BaseTask):
if cp.sections(): if cp.sections():
f = cStringIO.StringIO() f = cStringIO.StringIO()
cp.write(f) cp.write(f)
return f.getvalue() return dict(scm_credential=f.getvalue())
def build_passwords(self, inventory_update, **kwargs): def build_passwords(self, inventory_update, **kwargs):
"""Build a dictionary of authentication/credential information for """Build a dictionary of authentication/credential information for
@@ -1039,31 +1043,32 @@ class RunInventoryUpdate(BaseTask):
# `awx/plugins/inventory` directory; those files should be kept in # `awx/plugins/inventory` directory; those files should be kept in
# sync with those in Ansible core at all times. # sync with those in Ansible core at all times.
passwords = kwargs.get('passwords', {}) passwords = kwargs.get('passwords', {})
scm_credential = kwargs.get('private_data_files', {}).get('scm_credential', '')
if inventory_update.source == 'ec2': if inventory_update.source == 'ec2':
if passwords.get('source_username', '') and passwords.get('source_password', ''): if passwords.get('source_username', '') and passwords.get('source_password', ''):
env['AWS_ACCESS_KEY_ID'] = passwords['source_username'] env['AWS_ACCESS_KEY_ID'] = passwords['source_username']
env['AWS_SECRET_ACCESS_KEY'] = passwords['source_password'] env['AWS_SECRET_ACCESS_KEY'] = passwords['source_password']
env['EC2_INI_PATH'] = kwargs.get('private_data_file', '') env['EC2_INI_PATH'] = scm_credential
elif inventory_update.source == 'rax': elif inventory_update.source == 'rax':
env['RAX_CREDS_FILE'] = kwargs.get('private_data_file', '') env['RAX_CREDS_FILE'] = scm_credential
env['RAX_REGION'] = inventory_update.source_regions or 'all' env['RAX_REGION'] = inventory_update.source_regions or 'all'
# Set this environment variable so the vendored package won't # Set this environment variable so the vendored package won't
# complain about not being able to determine its version number. # complain about not being able to determine its version number.
env['PBR_VERSION'] = '0.5.21' env['PBR_VERSION'] = '0.5.21'
elif inventory_update.source == 'vmware': elif inventory_update.source == 'vmware':
env['VMWARE_INI'] = kwargs.get('private_data_file', '') env['VMWARE_INI'] = scm_credential
env['VMWARE_HOST'] = passwords.get('source_host', '') env['VMWARE_HOST'] = passwords.get('source_host', '')
env['VMWARE_USER'] = passwords.get('source_username', '') env['VMWARE_USER'] = passwords.get('source_username', '')
env['VMWARE_PASSWORD'] = passwords.get('source_password', '') env['VMWARE_PASSWORD'] = passwords.get('source_password', '')
elif inventory_update.source == 'azure': elif inventory_update.source == 'azure':
env['AZURE_SUBSCRIPTION_ID'] = passwords.get('source_username', '') env['AZURE_SUBSCRIPTION_ID'] = passwords.get('source_username', '')
env['AZURE_CERT_PATH'] = kwargs['private_data_file'] env['AZURE_CERT_PATH'] = scm_credential
elif inventory_update.source == 'gce': elif inventory_update.source == 'gce':
env['GCE_EMAIL'] = passwords.get('source_username', '') env['GCE_EMAIL'] = passwords.get('source_username', '')
env['GCE_PROJECT'] = passwords.get('source_project', '') env['GCE_PROJECT'] = passwords.get('source_project', '')
env['GCE_PEM_FILE_PATH'] = kwargs['private_data_file'] env['GCE_PEM_FILE_PATH'] = scm_credential
elif inventory_update.source == 'openstack': elif inventory_update.source == 'openstack':
env['OPENSTACK_CONFIG_FILE'] = kwargs.get('private_data_file', '') env['OPENSTACK_CONFIG_FILE'] = scm_credential
elif inventory_update.source == 'file': elif inventory_update.source == 'file':
# FIXME: Parse source_env to dict, update env. # FIXME: Parse source_env to dict, update env.
pass pass
@@ -1164,7 +1169,6 @@ class RunInventoryUpdate(BaseTask):
def get_idle_timeout(self): def get_idle_timeout(self):
return getattr(settings, 'INVENTORY_UPDATE_IDLE_TIMEOUT', None) return getattr(settings, 'INVENTORY_UPDATE_IDLE_TIMEOUT', None)
class RunAdHocCommand(BaseTask): class RunAdHocCommand(BaseTask):
''' '''
Celery task to run an ad hoc command using ansible. Celery task to run an ad hoc command using ansible.
@@ -1181,8 +1185,10 @@ class RunAdHocCommand(BaseTask):
# If we were sent SSH credentials, decrypt them and send them # If we were sent SSH credentials, decrypt them and send them
# back (they will be written to a temporary file). # back (they will be written to a temporary file).
creds = ad_hoc_command.credential creds = ad_hoc_command.credential
if creds: private_data = {}
return decrypt_field(creds, 'ssh_key_data') or None if creds and creds.ssh_key_data not in (None, ''):
private_data['ad_hoc_credential'] = decrypt_field(creds, 'ssh_key_data') or ''
return private_data
def build_passwords(self, ad_hoc_command, **kwargs): def build_passwords(self, ad_hoc_command, **kwargs):
''' '''
@@ -1322,7 +1328,7 @@ class RunAdHocCommand(BaseTask):
''' '''
If using an SSH key, return the path for use by ssh-agent. If using an SSH key, return the path for use by ssh-agent.
''' '''
return kwargs.get('private_data_file', '') return kwargs.get('private_data_files', {}).get('ad_hoc_credential', '')
def should_use_proot(self, instance, **kwargs): def should_use_proot(self, instance, **kwargs):
''' '''