mirror of
https://github.com/ansible/awx.git
synced 2026-03-03 01:38:50 -03:30
AC-1060 Add API support for vault password.
This commit is contained in:
@@ -980,12 +980,13 @@ class CredentialSerializer(BaseSerializer):
|
|||||||
ssh_key_data = serializers.WritableField(required=False, default='')
|
ssh_key_data = serializers.WritableField(required=False, default='')
|
||||||
ssh_key_unlock = serializers.WritableField(required=False, default='')
|
ssh_key_unlock = serializers.WritableField(required=False, default='')
|
||||||
sudo_password = serializers.WritableField(required=False, default='')
|
sudo_password = serializers.WritableField(required=False, default='')
|
||||||
|
vault_password = serializers.WritableField(required=False, default='')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Credential
|
model = Credential
|
||||||
fields = ('*', 'user', 'team', 'kind', 'cloud', 'username',
|
fields = ('*', 'user', 'team', 'kind', 'cloud', 'username',
|
||||||
'password', 'ssh_key_data', 'ssh_key_unlock',
|
'password', 'ssh_key_data', 'ssh_key_unlock',
|
||||||
'sudo_username', 'sudo_password')
|
'sudo_username', 'sudo_password', 'vault_password')
|
||||||
|
|
||||||
def to_native(self, obj):
|
def to_native(self, obj):
|
||||||
ret = super(CredentialSerializer, self).to_native(obj)
|
ret = super(CredentialSerializer, self).to_native(obj)
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ class Credential(CommonModelNameNotUnique):
|
|||||||
]
|
]
|
||||||
|
|
||||||
PASSWORD_FIELDS = ('password', 'ssh_key_data', 'ssh_key_unlock',
|
PASSWORD_FIELDS = ('password', 'ssh_key_data', 'ssh_key_unlock',
|
||||||
'sudo_password')
|
'sudo_password', 'vault_password')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'main'
|
app_label = 'main'
|
||||||
@@ -263,10 +263,14 @@ class Credential(CommonModelNameNotUnique):
|
|||||||
def needs_sudo_password(self):
|
def needs_sudo_password(self):
|
||||||
return self.kind == 'ssh' and self.sudo_password == 'ASK'
|
return self.kind == 'ssh' and self.sudo_password == 'ASK'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def needs_vault_password(self):
|
||||||
|
return self.kind == 'ssh' and self.vault_password == 'ASK'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def passwords_needed(self):
|
def passwords_needed(self):
|
||||||
needed = []
|
needed = []
|
||||||
for field in ('password', 'sudo_password', 'ssh_key_unlock'):
|
for field in ('password', 'sudo_password', 'ssh_key_unlock', 'vault_password'):
|
||||||
if getattr(self, 'needs_%s' % field):
|
if getattr(self, 'needs_%s' % field):
|
||||||
needed.append(field)
|
needed.append(field)
|
||||||
return needed
|
return needed
|
||||||
|
|||||||
@@ -176,9 +176,13 @@ class ProjectOptions(models.Model):
|
|||||||
# show up.
|
# show up.
|
||||||
matched = False
|
matched = False
|
||||||
try:
|
try:
|
||||||
for line in file(playbook):
|
for n, line in enumerate(file(playbook)):
|
||||||
if valid_re.match(line):
|
if valid_re.match(line):
|
||||||
matched = True
|
matched = True
|
||||||
|
# Any YAML file can also be encrypted with vault;
|
||||||
|
# allow these to be used as the main playbook.
|
||||||
|
elif n == 0 and line.startswith('$ANSIBLE_VAULT;'):
|
||||||
|
matched = True
|
||||||
except IOError:
|
except IOError:
|
||||||
continue
|
continue
|
||||||
if not matched:
|
if not matched:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import ConfigParser
|
import ConfigParser
|
||||||
import cStringIO
|
import cStringIO
|
||||||
import datetime
|
import datetime
|
||||||
import distutils.version
|
from distutils.version import StrictVersion as Version
|
||||||
import functools
|
import functools
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
@@ -317,6 +317,8 @@ class BaseTask(Task):
|
|||||||
instance = self.update_model(pk)
|
instance = self.update_model(pk)
|
||||||
status = instance.status
|
status = instance.status
|
||||||
raise RuntimeError('not starting %s task' % instance.status)
|
raise RuntimeError('not starting %s task' % instance.status)
|
||||||
|
# Fetch ansible version once here to support version-dependent features.
|
||||||
|
kwargs['ansible_version'] = get_ansible_version()
|
||||||
kwargs['private_data_file'] = self.build_private_data_file(instance, **kwargs)
|
kwargs['private_data_file'] = self.build_private_data_file(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)
|
||||||
@@ -377,12 +379,13 @@ class RunJob(BaseTask):
|
|||||||
|
|
||||||
def build_passwords(self, job, **kwargs):
|
def build_passwords(self, job, **kwargs):
|
||||||
'''
|
'''
|
||||||
Build a dictionary of passwords for SSH private key, SSH user and sudo.
|
Build a dictionary of passwords for SSH private key, SSH user, sudo
|
||||||
|
and ansible-vault.
|
||||||
'''
|
'''
|
||||||
passwords = super(RunJob, self).build_passwords(job, **kwargs)
|
passwords = super(RunJob, self).build_passwords(job, **kwargs)
|
||||||
creds = job.credential
|
creds = job.credential
|
||||||
if creds:
|
if creds:
|
||||||
for field in ('ssh_key_unlock', 'ssh_password', 'sudo_password'):
|
for field in ('ssh_key_unlock', 'ssh_password', 'sudo_password', 'vault_password'):
|
||||||
if field == 'ssh_password':
|
if field == 'ssh_password':
|
||||||
value = kwargs.get(field, decrypt_field(creds, 'password'))
|
value = kwargs.get(field, decrypt_field(creds, 'password'))
|
||||||
else:
|
else:
|
||||||
@@ -413,8 +416,7 @@ class RunJob(BaseTask):
|
|||||||
# When using Ansible >= 1.3, allow the inventory script to include host
|
# When using Ansible >= 1.3, allow the inventory script to include host
|
||||||
# variables inline via ['_meta']['hostvars'].
|
# variables inline via ['_meta']['hostvars'].
|
||||||
try:
|
try:
|
||||||
Version = distutils.version.StrictVersion
|
if Version(kwargs['ansible_version']) >= Version('1.3'):
|
||||||
if Version(get_ansible_version()) >= Version('1.3'):
|
|
||||||
env['INVENTORY_HOSTVARS'] = str(True)
|
env['INVENTORY_HOSTVARS'] = str(True)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
@@ -461,6 +463,15 @@ class RunJob(BaseTask):
|
|||||||
args.extend(['-U', sudo_username])
|
args.extend(['-U', sudo_username])
|
||||||
if 'sudo_password' in kwargs.get('passwords', {}):
|
if 'sudo_password' in kwargs.get('passwords', {}):
|
||||||
args.append('--ask-sudo-pass')
|
args.append('--ask-sudo-pass')
|
||||||
|
|
||||||
|
# When using Ansible >= 1.5, support prompting for a vault password.
|
||||||
|
try:
|
||||||
|
if Version(kwargs['ansible_version']) >= Version('1.5'):
|
||||||
|
if 'vault_password' in kwargs.get('passwords', {}):
|
||||||
|
args.append('--ask-vault-pass')
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
if job.forks: # FIXME: Max limit?
|
if job.forks: # FIXME: Max limit?
|
||||||
args.append('--forks=%d' % job.forks)
|
args.append('--forks=%d' % job.forks)
|
||||||
if job.limit:
|
if job.limit:
|
||||||
@@ -497,6 +508,7 @@ class RunJob(BaseTask):
|
|||||||
d[re.compile(r'^sudo password.*:\s*?$', re.M)] = 'sudo_password'
|
d[re.compile(r'^sudo password.*:\s*?$', re.M)] = 'sudo_password'
|
||||||
d[re.compile(r'^SSH password:\s*?$', re.M)] = 'ssh_password'
|
d[re.compile(r'^SSH password:\s*?$', re.M)] = 'ssh_password'
|
||||||
d[re.compile(r'^Password:\s*?$', re.M)] = 'ssh_password'
|
d[re.compile(r'^Password:\s*?$', re.M)] = 'ssh_password'
|
||||||
|
d[re.compile(r'^Vault password:\s*?$', re.M)] = 'vault_password'
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def pre_run_check(self, job, **kwargs):
|
def pre_run_check(self, job, **kwargs):
|
||||||
@@ -591,8 +603,7 @@ class RunProjectUpdate(BaseTask):
|
|||||||
# the git module.
|
# the git module.
|
||||||
if scm_type == 'git' and scm_url_parts.scheme == 'ssh':
|
if scm_type == 'git' and scm_url_parts.scheme == 'ssh':
|
||||||
try:
|
try:
|
||||||
Version = distutils.version.StrictVersion
|
if Version(kwargs['ansible_version']) >= Version('1.5'):
|
||||||
if Version(get_ansible_version()) >= Version('1.5'):
|
|
||||||
extra_vars['scm_accept_hostkey'] = 'true'
|
extra_vars['scm_accept_hostkey'] = 'true'
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from awx.main.models import *
|
|||||||
from awx.main.backend import LDAPSettings
|
from awx.main.backend import LDAPSettings
|
||||||
from awx.main.management.commands.run_callback_receiver import run_subscriber
|
from awx.main.management.commands.run_callback_receiver import run_subscriber
|
||||||
from awx.main.management.commands.run_task_system import run_taskmanager
|
from awx.main.management.commands.run_task_system import run_taskmanager
|
||||||
|
from awx.main.utils import get_ansible_version
|
||||||
|
|
||||||
|
|
||||||
class BaseTestMixin(object):
|
class BaseTestMixin(object):
|
||||||
@@ -40,6 +41,8 @@ class BaseTestMixin(object):
|
|||||||
self._temp_project_dirs = []
|
self._temp_project_dirs = []
|
||||||
self._current_auth = None
|
self._current_auth = None
|
||||||
self._user_passwords = {}
|
self._user_passwords = {}
|
||||||
|
self.ansible_version = get_ansible_version()
|
||||||
|
self.assertNotEqual(self.ansible_version, 'unknown')
|
||||||
# Wrap settings so we can redefine them within each test.
|
# Wrap settings so we can redefine them within each test.
|
||||||
self._wrapped = settings._wrapped
|
self._wrapped = settings._wrapped
|
||||||
settings._wrapped = UserSettingsHolder(settings._wrapped)
|
settings._wrapped = UserSettingsHolder(settings._wrapped)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
|
from distutils.version import StrictVersion as Version
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -87,6 +88,19 @@ TEST_ASYNC_NOWAIT_PLAYBOOK = '''
|
|||||||
poll: 0
|
poll: 0
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
TEST_VAULT_PLAYBOOK = '''$ANSIBLE_VAULT;1.1;AES256
|
||||||
|
35623233333035633365383330323835353564346534363762366465316263363463396162656432
|
||||||
|
6562643539396330616265616532656466353639303338650a313466333663646431646663333739
|
||||||
|
32623935316439343636633462373633653039646336376361386439386661366434333830383634
|
||||||
|
6266613530626633390a363532373562353262323863343830343865303663306335643430396239
|
||||||
|
63393963623537326366663332656132653465646332343234656237316537643135313932623237
|
||||||
|
66313863396463343232383131633531363239396636363165646562396261626633326561313837
|
||||||
|
32383634326230656230386237333561373630343233353239613463626538356338326633386434
|
||||||
|
36396639313030336165366266646431306665336662663732313762663938666239663233393964
|
||||||
|
30393733393331383132306463656636396566373961383865643562383564356363'''
|
||||||
|
|
||||||
|
TEST_VAULT_PASSWORD = '1234'
|
||||||
|
|
||||||
TEST_SSH_KEY_DATA = '''-----BEGIN RSA PRIVATE KEY-----
|
TEST_SSH_KEY_DATA = '''-----BEGIN RSA PRIVATE KEY-----
|
||||||
MIIEpQIBAAKCAQEAyQ8F5bbgjHvk4SZJsKI9OmJKMFxZqRhvx4LaqjLTKbBwRBsY
|
MIIEpQIBAAKCAQEAyQ8F5bbgjHvk4SZJsKI9OmJKMFxZqRhvx4LaqjLTKbBwRBsY
|
||||||
1/C00NPiZn70dKbeyV7RNVZxuzM6yd3D3lwTdbDu/eJ0x72t3ch+TdLt/aenyy10
|
1/C00NPiZn70dKbeyV7RNVZxuzM6yd3D3lwTdbDu/eJ0x72t3ch+TdLt/aenyy10
|
||||||
@@ -198,6 +212,7 @@ class RunJobTest(BaseCeleryTest):
|
|||||||
'password': '',
|
'password': '',
|
||||||
'sudo_username': '',
|
'sudo_username': '',
|
||||||
'sudo_password': '',
|
'sudo_password': '',
|
||||||
|
'vault_password': '',
|
||||||
}
|
}
|
||||||
opts.update(kwargs)
|
opts.update(kwargs)
|
||||||
self.credential = Credential.objects.create(**opts)
|
self.credential = Credential.objects.create(**opts)
|
||||||
@@ -845,6 +860,56 @@ class RunJobTest(BaseCeleryTest):
|
|||||||
self.assertTrue('ssh-agent' in job.job_args)
|
self.assertTrue('ssh-agent' in job.job_args)
|
||||||
self.assertTrue('Bad passphrase' not in job.result_stdout)
|
self.assertTrue('Bad passphrase' not in job.result_stdout)
|
||||||
|
|
||||||
|
def test_vault_password(self):
|
||||||
|
self.create_test_credential(vault_password=TEST_VAULT_PASSWORD)
|
||||||
|
self.create_test_project(TEST_VAULT_PLAYBOOK)
|
||||||
|
job_template = self.create_test_job_template()
|
||||||
|
job = self.create_test_job(job_template=job_template)
|
||||||
|
self.assertEqual(job.status, 'new')
|
||||||
|
self.assertFalse(job.passwords_needed_to_start)
|
||||||
|
self.assertTrue(job.signal_start())
|
||||||
|
job = Job.objects.get(pk=job.pk)
|
||||||
|
if Version(self.ansible_version) >= Version('1.5'):
|
||||||
|
self.check_job_result(job, 'successful')
|
||||||
|
self.assertTrue('--ask-vault-pass' in job.job_args)
|
||||||
|
else:
|
||||||
|
self.check_job_result(job, 'failed')
|
||||||
|
self.assertFalse('--ask-vault-pass' in job.job_args)
|
||||||
|
|
||||||
|
def test_vault_ask_password(self):
|
||||||
|
self.create_test_credential(vault_password='ASK')
|
||||||
|
self.create_test_project(TEST_VAULT_PLAYBOOK)
|
||||||
|
job_template = self.create_test_job_template()
|
||||||
|
job = self.create_test_job(job_template=job_template)
|
||||||
|
self.assertEqual(job.status, 'new')
|
||||||
|
self.assertTrue(job.passwords_needed_to_start)
|
||||||
|
self.assertTrue('vault_password' in job.passwords_needed_to_start)
|
||||||
|
self.assertFalse(job.signal_start())
|
||||||
|
self.assertEqual(job.status, 'new')
|
||||||
|
self.assertTrue(job.signal_start(vault_password=TEST_VAULT_PASSWORD))
|
||||||
|
job = Job.objects.get(pk=job.pk)
|
||||||
|
if Version(self.ansible_version) >= Version('1.5'):
|
||||||
|
self.check_job_result(job, 'successful')
|
||||||
|
self.assertTrue('--ask-vault-pass' in job.job_args)
|
||||||
|
else:
|
||||||
|
self.check_job_result(job, 'failed')
|
||||||
|
self.assertFalse('--ask-vault-pass' in job.job_args)
|
||||||
|
|
||||||
|
def test_vault_bad_password(self):
|
||||||
|
self.create_test_credential(vault_password='not it')
|
||||||
|
self.create_test_project(TEST_VAULT_PLAYBOOK)
|
||||||
|
job_template = self.create_test_job_template()
|
||||||
|
job = self.create_test_job(job_template=job_template)
|
||||||
|
self.assertEqual(job.status, 'new')
|
||||||
|
self.assertFalse(job.passwords_needed_to_start)
|
||||||
|
self.assertTrue(job.signal_start())
|
||||||
|
job = Job.objects.get(pk=job.pk)
|
||||||
|
self.check_job_result(job, 'failed')
|
||||||
|
if Version(self.ansible_version) >= Version('1.5'):
|
||||||
|
self.assertTrue('--ask-vault-pass' in job.job_args)
|
||||||
|
else:
|
||||||
|
self.assertFalse('--ask-vault-pass' in job.job_args)
|
||||||
|
|
||||||
def _test_cloud_credential_environment_variables(self, kind):
|
def _test_cloud_credential_environment_variables(self, kind):
|
||||||
if kind == 'aws':
|
if kind == 'aws':
|
||||||
env_var1 = 'AWS_ACCESS_KEY'
|
env_var1 = 'AWS_ACCESS_KEY'
|
||||||
|
|||||||
Reference in New Issue
Block a user