diff --git a/awx/main/tests/functional/api/test_job_runtime_params.py b/awx/main/tests/functional/api/test_job_runtime_params.py index 581a31efad..3170ce01e0 100644 --- a/awx/main/tests/functional/api/test_job_runtime_params.py +++ b/awx/main/tests/functional/api/test_job_runtime_params.py @@ -370,6 +370,56 @@ def test_job_launch_fails_with_missing_vault_password(machine_credential, vault_ assert response.data['passwords_needed_to_start'] == ['vault_password'] +@pytest.mark.django_db +@pytest.mark.parametrize('launch_kwargs', [ + {'vault_password.abc': 'vault-me-1', 'vault_password.xyz': 'vault-me-2'}, + {'credential_passwords': {'vault_password.abc': 'vault-me-1', 'vault_password.xyz': 'vault-me-2'}} +]) +def test_job_launch_fails_with_missing_multivault_password(machine_credential, vault_credential, + deploy_jobtemplate, launch_kwargs, + get, post, rando): + vault_cred_first = Credential( + name='Vault #1', + credential_type=vault_credential.credential_type, + inputs={ + 'vault_password': 'ASK', + 'vault_id': 'abc' + } + ) + vault_cred_first.save() + vault_cred_second = Credential( + name='Vault #2', + credential_type=vault_credential.credential_type, + inputs={ + 'vault_password': 'ASK', + 'vault_id': 'xyz' + } + ) + vault_cred_second.save() + deploy_jobtemplate.credentials.add(vault_cred_first) + deploy_jobtemplate.credentials.add(vault_cred_second) + deploy_jobtemplate.execute_role.members.add(rando) + deploy_jobtemplate.save() + + url = reverse('api:job_template_launch', kwargs={'pk': deploy_jobtemplate.pk}) + resp = get(url, rando, expect=200) + assert resp.data['passwords_needed_to_start'] == ['vault_password.abc', 'vault_password.xyz'] + assert sum([ + cred['passwords_needed'] for cred in resp.data['defaults']['credentials'] + if cred['credential_type'] == vault_credential.credential_type_id + ], []) == ['vault_password.abc', 'vault_password.xyz'] + + resp = post(url, rando, expect=400) + assert resp.data['passwords_needed_to_start'] == ['vault_password.abc', 'vault_password.xyz'] + + with mock.patch.object(Job, 'signal_start') as signal_start: + post(url, launch_kwargs, rando, expect=201) + signal_start.assert_called_with(**{ + 'vault_password.abc': 'vault-me-1', + 'vault_password.xyz': 'vault-me-2' + }) + + @pytest.mark.django_db def test_job_launch_fails_with_missing_ssh_password(machine_credential, deploy_jobtemplate, post, rando): diff --git a/docs/multi_credential_assignment.md b/docs/multi_credential_assignment.md index 62b1ad73d7..ca7bfa2353 100644 --- a/docs/multi_credential_assignment.md +++ b/docs/multi_credential_assignment.md @@ -161,3 +161,62 @@ deprecated,backwards compatible support for specifying credentials at launch tim via the `credential`, `vault_credential`, and `extra_credentials` fields: `POST /api/v2/job_templates/N/launch/ {'credential': A, 'vault_credential': B, 'extra_credentials': [C, D]}` + + +Specifying Multiple Vault Credentials +------------------------------------- +One interesting use case supported by the new "zero or more credentials" model +is the ability to assign multiple Vault credentials to a Job Template run. + +This specific use case covers Ansible's support for multiple vault passwords for +a playbook run (since Ansible 2.4): +http://docs.ansible.com/ansible/latest/vault.html#vault-ids-and-multiple-vault-passwords + +Vault credentials in awx now have an optional field, `vault_id`, which is +analogous to the `--vault-id` argument to `ansible-playbook`. To run +a playbook which makes use of multiple vault passwords: + +1. Make a Vault credential in Tower for each vault password; specify the Vault + ID as a field on the credential and input the password (which will be + encrypted and stored). +2. Assign multiple vault credentials to the job template via the new + `credentials` endpoint: + + ``` + POST /api/v2/job_templates/N/credentials/ + + { + 'associate': true, + 'id': X + } + ``` +3. Launch the job template, and `ansible-playbook` will be invoked with + multiple `--vault-id` arguments. + +Prompted Vault Credentials +-------------------------- +Vault credentials can have passwords that are marked as "Prompt on launch". +When this is the case, the launch endpoint of any related Job Templates will +communicate necessary Vault passwords via the `passwords_needed_to_start` key: + +``` +GET /api/v2/job_templates/N/launch/ +{ + 'passwords_needed_to_start': [ + 'vault_password.X', + 'vault_password.Y', + ] +} +``` + +...where `X` and `Y` are primary keys of the associated Vault credentials. + +``` +POST /api/v2/job_templates/N/launch/ +{ + 'credential_passwords': { + 'vault_password.X': 'first-vault-password' + 'vault_password.Y': 'second-vault-password' + } +} +```