-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* update comment in JWT auth * add unit tests for userpass auth * add integration tests for userpass auth * fix localenv coverage regression
- Loading branch information
Showing
11 changed files
with
300 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
vault/auth/userpass | ||
context/target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
--- | ||
ansible_hashi_vault_url: '{{ vault_test_server_http }}' | ||
ansible_hashi_vault_auth_method: userpass | ||
|
||
auth_paths: | ||
- userpass | ||
- userpass-alt | ||
|
||
userpass_username: testuser | ||
userpass_password: testpass | ||
|
||
vault_userpass_canary: | ||
path: cubbyhole/configure_userpass | ||
value: complete # value does not matter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
--- | ||
dependencies: | ||
- setup_vault_test_plugins | ||
- setup_vault_configure |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
--- | ||
# task vars are not templated when used as vars, so we'll need to set_fact this evaluate the template | ||
# see: https://github.com/ansible/ansible/issues/73268 | ||
- name: Persist defaults | ||
set_fact: | ||
'{{ item.key }}': "{{ lookup('vars', item.key) }}" | ||
loop: "{{ lookup('file', role_path ~ '/defaults/main.yml') | from_yaml | dict2items }}" | ||
loop_control: | ||
label: '{{ item.key }}' | ||
|
||
- name: Configuration tasks | ||
module_defaults: | ||
vault_ci_enable_auth: '{{ vault_plugins_module_defaults_common }}' | ||
vault_ci_policy_put: '{{ vault_plugins_module_defaults_common }}' | ||
vault_ci_write: '{{ vault_plugins_module_defaults_common }}' | ||
vault_ci_read: '{{ vault_plugins_module_defaults_common }}' | ||
block: | ||
- name: Canary for userpass auth | ||
vault_ci_read: | ||
path: '{{ vault_userpass_canary.path }}' | ||
register: canary | ||
|
||
- name: Configure userpass | ||
when: canary.result is none | ||
loop: '{{ auth_paths }}' | ||
include_tasks: | ||
file: userpass_setup.yml | ||
apply: | ||
vars: | ||
default_path: '{{ ansible_hashi_vault_auth_method }}' | ||
this_path: '{{ item }}' | ||
|
||
- name: Write Canary | ||
when: canary.result is none | ||
vault_ci_write: | ||
path: '{{ vault_userpass_canary.path }}' | ||
data: | ||
value: '{{ vault_userpass_canary.value }}' | ||
|
||
- name: Run userpass tests | ||
loop: '{{ auth_paths | product(["target", "controller"]) | list }}' | ||
include_tasks: | ||
file: userpass_test_{{ item[1] }}.yml | ||
apply: | ||
vars: | ||
default_path: '{{ ansible_hashi_vault_auth_method }}' | ||
this_path: '{{ item[0] }}' | ||
module_defaults: | ||
assert: | ||
quiet: yes |
27 changes: 27 additions & 0 deletions
27
tests/integration/targets/auth_userpass/tasks/userpass_setup.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
- name: "Setup block" | ||
vars: | ||
is_default_path: "{{ this_path == default_path }}" | ||
block: | ||
- name: 'Enable the userpass auth method' | ||
vault_ci_enable_auth: | ||
method_type: userpass | ||
path: '{{ omit if is_default_path else this_path }}' | ||
config: | ||
default_lease_ttl: 60m | ||
|
||
- name: 'Create a userpass policy' | ||
vault_ci_policy_put: | ||
name: userpass-policy | ||
policy: | | ||
path "auth/{{ this_path }}/login" { | ||
capabilities = [ "create", "read" ] | ||
} | ||
- name: 'Create a named role' | ||
vault_ci_write: | ||
path: 'auth/{{ this_path }}/users/{{ userpass_username }}' | ||
data: | ||
# in docs, this is token_policies (changed in Vault 1.2) | ||
# use 'policies' to support older versions | ||
policies: "{{ 'test-policy' if is_default_path else 'alt-policy' }},userpass-policy" | ||
password: '{{ userpass_password }}' |
32 changes: 32 additions & 0 deletions
32
tests/integration/targets/auth_userpass/tasks/userpass_test_controller.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
- name: "Test block" | ||
vars: | ||
is_default_path: "{{ this_path == default_path }}" | ||
kwargs_mount: "{{ {} if is_default_path else {'mount_point': this_path} }}" | ||
kwargs_common: | ||
username: '{{ userpass_username }}' | ||
kwargs: "{{ kwargs_common | combine(kwargs_mount) }}" | ||
block: | ||
# the purpose of this test is to catch when the plugin accepts mount_point but does not pass it into hvac | ||
# we set the policy of the default mount to deny access to this secret and so we expect failure when the mount | ||
# is default, and success when the mount is alternate | ||
- name: Check auth mount differing result | ||
set_fact: | ||
response: "{{ lookup('vault_test_auth', '', password=userpass_password, **kwargs) }}" | ||
|
||
- assert: | ||
fail_msg: "A token from mount path '{{ this_path }}' had the wrong policy: {{ response.login.auth.policies }}" | ||
that: | ||
- ('test-policy' in response.login.auth.policies) | bool == is_default_path | ||
- ('test-policy' not in response.login.auth.policies) | bool != is_default_path | ||
- ('alt-policy' in response.login.auth.policies) | bool != is_default_path | ||
- ('alt-policy' not in response.login.auth.policies) | bool == is_default_path | ||
|
||
- name: Failure expected when erroneous credentials are used | ||
set_fact: | ||
response: "{{ lookup('vault_test_auth', '', password='fake', want_exception=true, **kwargs) }}" | ||
|
||
- assert: | ||
fail_msg: "An invalid password somehow did not cause a failure." | ||
that: | ||
- response is failed | ||
- response.msg is search('invalid username or password') |
37 changes: 37 additions & 0 deletions
37
tests/integration/targets/auth_userpass/tasks/userpass_test_target.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
- name: "Test block" | ||
vars: | ||
is_default_path: "{{ this_path == default_path }}" | ||
module_defaults: | ||
vault_test_auth: | ||
url: '{{ ansible_hashi_vault_url }}' | ||
auth_method: '{{ ansible_hashi_vault_auth_method }}' | ||
mount_point: '{{ omit if is_default_path else this_path }}' | ||
username: '{{ userpass_username }}' | ||
password: '{{ userpass_password }}' | ||
block: | ||
# the purpose of this test is to catch when the plugin accepts mount_point but does not pass it into hvac | ||
# we set the policy of the default mount to deny access to this secret and so we expect failure when the mount | ||
# is default, and success when the mount is alternate | ||
- name: Check auth mount differing result | ||
register: response | ||
vault_test_auth: | ||
|
||
- assert: | ||
fail_msg: "A token from mount path '{{ this_path }}' had the wrong policy: {{ response.login.auth.policies }}" | ||
that: | ||
- ('test-policy' in response.login.auth.policies) | bool == is_default_path | ||
- ('test-policy' not in response.login.auth.policies) | bool != is_default_path | ||
- ('alt-policy' in response.login.auth.policies) | bool != is_default_path | ||
- ('alt-policy' not in response.login.auth.policies) | bool == is_default_path | ||
|
||
- name: Failure expected when erroneous credentials are used | ||
register: response | ||
vault_test_auth: | ||
password: fake | ||
want_exception: yes | ||
|
||
- assert: | ||
fail_msg: "An invalid password somehow did not cause a failure." | ||
that: | ||
- response.inner is failed | ||
- response.msg is search('invalid username or password') |
31 changes: 31 additions & 0 deletions
31
tests/unit/plugins/module_utils/authentication/fixtures/userpass_login_response.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{ | ||
"auth": { | ||
"accessor": "mQewzgKRx5Yui1h1eMemJlMu", | ||
"client_token": "s.drgLxu6ZtttSVn5Zkoy0huMR", | ||
"entity_id": "8a74ffd3-f71b-8ebe-7942-610428051ea9", | ||
"lease_duration": 3600, | ||
"metadata": { | ||
"username": "testuser" | ||
}, | ||
"orphan": true, | ||
"policies": [ | ||
"alt-policy", | ||
"default", | ||
"userpass-policy" | ||
], | ||
"renewable": true, | ||
"token_policies": [ | ||
"alt-policy", | ||
"default", | ||
"userpass-policy" | ||
], | ||
"token_type": "service" | ||
}, | ||
"data": null, | ||
"lease_duration": 0, | ||
"lease_id": "", | ||
"renewable": false, | ||
"request_id": "511e8fba-83f0-4b7e-95ea-770aa19c1957", | ||
"warnings": null, | ||
"wrap_info": null | ||
} |
98 changes: 98 additions & 0 deletions
98
tests/unit/plugins/module_utils/authentication/test_auth_userpass.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# -*- coding: utf-8 -*- | ||
# Copyright (c) 2021 Brian Scholer (@briantist) | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
|
||
from __future__ import (absolute_import, division, print_function) | ||
__metaclass__ = type | ||
|
||
import pytest | ||
|
||
from ansible_collections.community.hashi_vault.tests.unit.compat import mock | ||
|
||
from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_userpass import ( | ||
HashiVaultAuthMethodUserpass, | ||
) | ||
|
||
from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import ( | ||
HashiVaultAuthMethodBase, | ||
HashiVaultValueError, | ||
) | ||
|
||
|
||
@pytest.fixture | ||
def option_dict(): | ||
return { | ||
'auth_method': 'userpass', | ||
'username': None, | ||
'password': None, | ||
'mount_point': None, | ||
} | ||
|
||
|
||
@pytest.fixture | ||
def userpass_password(): | ||
return 'opaque' | ||
|
||
|
||
@pytest.fixture | ||
def userpass_username(): | ||
return 'fake-user' | ||
|
||
|
||
@pytest.fixture | ||
def auth_userpass(adapter, warner): | ||
return HashiVaultAuthMethodUserpass(adapter, warner) | ||
|
||
|
||
@pytest.fixture | ||
def userpass_login_response(fixture_loader): | ||
return fixture_loader('userpass_login_response.json') | ||
|
||
|
||
class TestAuthUserpass(object): | ||
|
||
def test_auth_userpass_is_auth_method_base(self, auth_userpass): | ||
assert isinstance(auth_userpass, HashiVaultAuthMethodUserpass) | ||
assert issubclass(HashiVaultAuthMethodUserpass, HashiVaultAuthMethodBase) | ||
|
||
def test_auth_userpass_validate_direct(self, auth_userpass, adapter, userpass_username, userpass_password): | ||
adapter.set_option('username', userpass_username) | ||
adapter.set_option('password', userpass_password) | ||
|
||
auth_userpass.validate() | ||
|
||
@pytest.mark.parametrize('opt_patch', [ | ||
{'username': 'user-only'}, | ||
{'password': 'password-only'}, | ||
]) | ||
def test_auth_userpass_validate_xfailures(self, auth_userpass, adapter, opt_patch): | ||
adapter.set_options(**opt_patch) | ||
|
||
with pytest.raises(HashiVaultValueError, match=r'Authentication method userpass requires options .*? to be set, but these are missing:'): | ||
auth_userpass.validate() | ||
|
||
@pytest.mark.parametrize('use_token', [True, False], ids=lambda x: 'use_token=%s' % x) | ||
@pytest.mark.parametrize('mount_point', [None, 'other'], ids=lambda x: 'mount_point=%s' % x) | ||
def test_auth_userpass_authenticate( | ||
self, auth_userpass, client, adapter, userpass_password, userpass_username, mount_point, use_token, userpass_login_response | ||
): | ||
adapter.set_option('username', userpass_username) | ||
adapter.set_option('password', userpass_password) | ||
adapter.set_option('mount_point', mount_point) | ||
|
||
expected_login_params = { | ||
'username': userpass_username, | ||
'password': userpass_password, | ||
} | ||
if mount_point: | ||
expected_login_params['mount_point'] = mount_point | ||
|
||
def _set_client_token(*args, **kwargs): | ||
return userpass_login_response | ||
|
||
with mock.patch.object(client.auth.userpass, 'login', side_effect=_set_client_token) as userpass_login: | ||
response = auth_userpass.authenticate(client, use_token=use_token) | ||
userpass_login.assert_called_once_with(**expected_login_params) | ||
|
||
assert response['auth']['client_token'] == userpass_login_response['auth']['client_token'] | ||
assert (client.token == userpass_login_response['auth']['client_token']) is use_token |