summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGaige B Paulsen <gaige@cluetrust.com>2025-01-02 14:06:58 -0500
committerGitHub <noreply@github.com>2025-01-02 19:06:58 +0000
commit9e159990f949652ec1b22f9a9a6e72828bdd1e80 (patch)
treef3f580083415d4ea48cf81b86e02f08df8f9f26a
parentdbd87e3ab89b7839e41df76c2fa7712855853fd3 (diff)
downloadvyos.vyos-9e159990f949652ec1b22f9a9a6e72828bdd1e80.tar.gz
vyos.vyos-9e159990f949652ec1b22f9a9a6e72828bdd1e80.zip
T6988: fix: remove role/level, fix tests (#371)
* T6988: fix: remove role/level, fix tests * feature: add support for SSH keys * tests: add integration tests for public_keys * feat: add encrypted password support * tests: add unit for encrypted * tests: fix wrapping in YAML * tests: fix smoke tests
-rw-r--r--changelogs/fragments/T6988-fix-user.yml15
-rw-r--r--docs/vyos.vyos.vyos_user_module.rst191
-rw-r--r--plugins/modules/vyos_user.py195
-rw-r--r--tests/integration/targets/vyos_smoke/tests/cli/caching.yaml8
-rw-r--r--tests/integration/targets/vyos_user/tests/cli/auth.yaml13
-rw-r--r--tests/integration/targets/vyos_user/tests/cli/basic.yaml6
-rw-r--r--tests/integration/targets/vyos_user/tests/cli/encrypted.yaml97
-rw-r--r--tests/integration/targets/vyos_user/tests/cli/public_keys.yaml129
-rw-r--r--tests/integration/targets/vyos_user/vars/main.yaml64
-rw-r--r--tests/unit/modules/network/vyos/fixtures/vyos_user_config.cfg6
-rw-r--r--tests/unit/modules/network/vyos/test_vyos_user.py132
11 files changed, 769 insertions, 87 deletions
diff --git a/changelogs/fragments/T6988-fix-user.yml b/changelogs/fragments/T6988-fix-user.yml
new file mode 100644
index 0000000..26ea007
--- /dev/null
+++ b/changelogs/fragments/T6988-fix-user.yml
@@ -0,0 +1,15 @@
+---
+breaking_changes:
+ - removal of role/level as it was removed in 1.3
+
+major_changes:
+ - add support for public-key authentication
+ - add support for encrypted password specification
+
+minor_changes:
+ - fix sending of `full-name` to use quotes
+ - fix parsing of `full-name` to ignore quotes
+ - fix integration tests for smoke
+
+known_issues:
+ - ssh login tests are brittle
diff --git a/docs/vyos.vyos.vyos_user_module.rst b/docs/vyos.vyos.vyos_user_module.rst
index 5f0ad83..f95200b 100644
--- a/docs/vyos.vyos.vyos_user_module.rst
+++ b/docs/vyos.vyos.vyos_user_module.rst
@@ -29,12 +29,12 @@ Parameters
<table border=0 cellpadding=0 class="documentation-table">
<tr>
- <th colspan="2">Parameter</th>
+ <th colspan="3">Parameter</th>
<th>Choices/<font color="blue">Defaults</font></th>
<th width="100%">Comments</th>
</tr>
<tr>
- <td colspan="2">
+ <td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>aggregate</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -52,7 +52,7 @@ Parameters
</tr>
<tr>
<td class="elbow-placeholder"></td>
- <td colspan="1">
+ <td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>configured_password</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -68,7 +68,23 @@ Parameters
</tr>
<tr>
<td class="elbow-placeholder"></td>
- <td colspan="1">
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>encrypted_password</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The encrypted password of the user account on the remote device. Note that unlike the <code>configured_password</code> argument, this argument ignores the <code>update_password</code> and updates if the value is different from the one in the device running config.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>full_name</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -84,23 +100,59 @@ Parameters
</tr>
<tr>
<td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The username to be configured on the VyOS device. This argument accepts a string value and is mutually exclusive with the <code>aggregate</code> argument.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>public_keys</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Public keys for authentiction over SSH.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
- <b>level</b>
+ <b>key</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
+ / <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
- <div>The <code>level</code> argument configures the level of the user when logged into the system. This argument accepts string values admin or operator.</div>
- <div style="font-size: small; color: darkgreen"><br/>aliases: role</div>
+ <div>Public key string (base64 encoded)</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>name</b>
@@ -113,13 +165,40 @@ Parameters
<td>
</td>
<td>
- <div>The username to be configured on the VyOS device. This argument accepts a string value and is mutually exclusive with the <code>aggregate</code> argument.</div>
+ <div>Name of the key (usually in the form of user@hostname)</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>type</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>ssh-dss</li>
+ <li>ssh-rsa</li>
+ <li>ecdsa-sha2-nistp256</li>
+ <li>ecdsa-sha2-nistp384</li>
+ <li>ssh-ed25519</li>
+ <li>ecdsa-sha2-nistp521</li>
+ </ul>
+ </td>
+ <td>
+ <div>Type of the key</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
<b>state</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
@@ -138,7 +217,7 @@ Parameters
</tr>
<tr>
<td class="elbow-placeholder"></td>
- <td colspan="1">
+ <td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>update_password</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -158,7 +237,7 @@ Parameters
</tr>
<tr>
- <td colspan="2">
+ <td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>configured_password</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -173,7 +252,22 @@ Parameters
</td>
</tr>
<tr>
- <td colspan="2">
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>encrypted_password</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The encrypted password of the user account on the remote device. Note that unlike the <code>configured_password</code> argument, this argument ignores the <code>update_password</code> and updates if the value is different from the one in the device running config.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>full_name</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -188,39 +282,99 @@ Parameters
</td>
</tr>
<tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The username to be configured on the VyOS device. This argument accepts a string value and is mutually exclusive with the <code>aggregate</code> argument.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>public_keys</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Public keys for authentiction over SSH.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
- <b>level</b>
+ <b>key</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
+ / <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
- <div>The <code>level</code> argument configures the level of the user when logged into the system. This argument accepts string values admin or operator.</div>
- <div style="font-size: small; color: darkgreen"><br/>aliases: role</div>
+ <div>Public key string (base64 encoded)</div>
</td>
</tr>
<tr>
+ <td class="elbow-placeholder"></td>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>name</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
+ / <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
- <div>The username to be configured on the VyOS device. This argument accepts a string value and is mutually exclusive with the <code>aggregate</code> argument.</div>
+ <div>Name of the key (usually in the form of user@hostname)</div>
</td>
</tr>
<tr>
+ <td class="elbow-placeholder"></td>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>type</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>ssh-dss</li>
+ <li>ssh-rsa</li>
+ <li>ecdsa-sha2-nistp256</li>
+ <li>ecdsa-sha2-nistp384</li>
+ <li>ssh-ed25519</li>
+ <li>ecdsa-sha2-nistp521</li>
+ </ul>
+ </td>
+ <td>
+ <div>Type of the key</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
<b>purge</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
@@ -238,7 +392,7 @@ Parameters
</td>
</tr>
<tr>
- <td colspan="2">
+ <td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>state</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -257,7 +411,7 @@ Parameters
</td>
</tr>
<tr>
- <td colspan="2">
+ <td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>update_password</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
@@ -307,7 +461,6 @@ Examples
aggregate:
- name: netop
- name: netend
- level: operator
state: present
- name: Change Password for User netop
vyos.vyos.vyos_user:
@@ -344,7 +497,7 @@ Common return values are documented `here <https://docs.ansible.com/ansible/late
<div>The list of configuration mode commands to send to the device</div>
<br/>
<div style="font-size: smaller"><b>Sample:</b></div>
- <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">[&#x27;set system login user test level operator&#x27;, &#x27;set system login user authentication plaintext-password password&#x27;]</div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">[&#x27;set system login user authentication plaintext-password password&#x27;]</div>
</td>
</tr>
</table>
diff --git a/plugins/modules/vyos_user.py b/plugins/modules/vyos_user.py
index 53c45c2..5aebf94 100644
--- a/plugins/modules/vyos_user.py
+++ b/plugins/modules/vyos_user.py
@@ -62,6 +62,12 @@ options:
- The C(full_name) argument provides the full name of the user account to be created
on the remote device. This argument accepts any text string value.
type: str
+ encrypted_password:
+ description:
+ - The encrypted password of the user account on the remote device. Note that unlike
+ the C(configured_password) argument, this argument ignores the C(update_password)
+ and updates if the value is different from the one in the device running config.
+ type: str
configured_password:
description:
- The password to be configured on the VyOS device. The password needs to be provided
@@ -77,13 +83,6 @@ options:
choices:
- on_create
- always
- level:
- description:
- - The C(level) argument configures the level of the user when logged into the
- system. This argument accepts string values admin or operator.
- type: str
- aliases:
- - role
state:
description:
- Configures the state of the username definition as it relates to the device
@@ -94,6 +93,32 @@ options:
choices:
- present
- absent
+ public_keys: &public_keys
+ description:
+ - Public keys for authentiction over SSH.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Name of the key (usually in the form of user@hostname)
+ required: true
+ type: str
+ key:
+ description: Public key string (base64 encoded)
+ required: true
+ type: str
+ type:
+ description: Type of the key
+ required: true
+ type: str
+ choices:
+ - ssh-dss
+ - ssh-rsa
+ - ecdsa-sha2-nistp256
+ - ecdsa-sha2-nistp384
+ - ssh-ed25519
+ - ecdsa-sha2-nistp521
+
name:
description:
- The username to be configured on the VyOS device. This argument accepts a string
@@ -104,6 +129,12 @@ options:
- The C(full_name) argument provides the full name of the user account to be created
on the remote device. This argument accepts any text string value.
type: str
+ encrypted_password:
+ description:
+ - The encrypted password of the user account on the remote device. Note that unlike
+ the C(configured_password) argument, this argument ignores the C(update_password)
+ and updates if the value is different from the one in the device running config.
+ type: str
configured_password:
description:
- The password to be configured on the VyOS device. The password needs to be provided
@@ -120,13 +151,7 @@ options:
choices:
- on_create
- always
- level:
- description:
- - The C(level) argument configures the level of the user when logged into the
- system. This argument accepts string values admin or operator.
- type: str
- aliases:
- - role
+ public_keys: *public_keys
purge:
description:
- Instructs the module to consider the resource definition absolute. It will remove
@@ -161,7 +186,6 @@ EXAMPLES = """
aggregate:
- name: netop
- name: netend
- level: operator
state: present
- name: Change Password for User netop
vyos.vyos.vyos_user:
@@ -177,7 +201,6 @@ commands:
returned: always
type: list
sample:
- - set system login user test level operator
- set system login user authentication plaintext-password password
"""
@@ -198,11 +221,6 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import
)
-def validate_level(value, module):
- if value not in ("admin", "operator"):
- module.fail_json(msg="level must be either admin or operator, got %s" % value)
-
-
def spec_to_commands(updates, module):
commands = list()
update_password = module.params["update_password"]
@@ -220,11 +238,39 @@ def spec_to_commands(updates, module):
commands.append("delete system login user %s" % want["name"])
continue
- if needs_update(want, have, "level"):
- add(commands, want, "level %s" % want["level"])
-
if needs_update(want, have, "full_name"):
- add(commands, want, "full-name %s" % want["full_name"])
+ add(commands, want, "full-name '%s'" % want["full_name"])
+
+ # look both ways for public_keys to handle replacement
+ want_keys = want.get("public_keys") or dict()
+ have_keys = have.get("public_keys") or dict()
+ for key_name in want_keys:
+ key = want_keys[key_name]
+ if key_name not in have_keys or key != have_keys[key_name]:
+ add(
+ commands,
+ want,
+ "authentication public-keys %s key '%s'" % (key["name"], key["key"]),
+ )
+ add(
+ commands,
+ want,
+ "authentication public-keys %s type '%s'" % (key["name"], key["type"]),
+ )
+
+ for key_name in have_keys:
+ if key_name not in want_keys:
+ commands.append(
+ "delete system login user %s authentication public-keys %s"
+ % (want["name"], key_name),
+ )
+
+ if needs_update(want, have, "encrypted_password"):
+ add(
+ commands,
+ want,
+ "authentication encrypted-password '%s'" % want["encrypted_password"],
+ )
if needs_update(want, have, "configured_password"):
if update_password == "always" or not have:
@@ -237,20 +283,57 @@ def spec_to_commands(updates, module):
return commands
-def parse_level(data):
- match = re.search(r"level (\S+)", data, re.M)
- if match:
- level = match.group(1)[1:-1]
- return level
-
-
def parse_full_name(data):
- match = re.search(r"full-name (\S+)", data, re.M)
+ match = re.search(r"full-name '(\S+)'", data, re.M)
if match:
full_name = match.group(1)[1:-1]
return full_name
+def parse_key(data):
+ match = re.search(r"key '(\S+)'", data, re.M)
+ if match:
+ key = match.group(1)
+ return key
+
+
+def parse_key_type(data):
+ match = re.search(r"type '(\S+)'", data, re.M)
+ if match:
+ key_type = match.group(1)
+ return key_type
+
+
+def parse_public_keys(data):
+ """
+ Parse public keys from the configuration
+ returning dictionary of dictionaries indexed by key name
+ """
+ match = re.findall(r"public-keys (\S+)", data, re.M)
+ if not match:
+ return dict()
+
+ keys = dict()
+ for key in set(match):
+ regex = r" %s .+$" % key
+ cfg = re.findall(regex, data, re.M)
+ cfg = "\n".join(cfg)
+ obj = {
+ "name": key,
+ "key": parse_key(cfg),
+ "type": parse_key_type(cfg),
+ }
+ keys[key] = obj
+ return keys
+
+
+def parse_encrypted_password(data):
+ match = re.search(r"authentication encrypted-password '(\S+)'", data, re.M)
+ if match:
+ encrypted_password = match.group(1)
+ return encrypted_password
+
+
def config_to_dict(module):
data = get_config(module)
@@ -268,8 +351,9 @@ def config_to_dict(module):
"name": user,
"state": "present",
"configured_password": None,
- "level": parse_level(cfg),
"full_name": parse_full_name(cfg),
+ "encrypted_password": parse_encrypted_password(cfg),
+ "public_keys": parse_public_keys(cfg),
}
instances.append(obj)
@@ -289,6 +373,21 @@ def get_param_value(key, item, module):
return value
+def map_key_params_to_dict(keys):
+ """
+ Map the list of keys to a dictionary of dictionaries
+ indexed by key name
+ """
+ all_keys = dict()
+ if keys is None:
+ return all_keys
+
+ for key in keys:
+ key_name = key["name"]
+ all_keys[key_name] = key
+ return all_keys
+
+
def map_params_to_obj(module):
aggregate = module.params["aggregate"]
if not aggregate:
@@ -309,9 +408,10 @@ def map_params_to_obj(module):
for item in users:
get_value = partial(get_param_value, item=item, module=module)
item["configured_password"] = get_value("configured_password")
+ item["encrypted_password"] = get_value("encrypted_password")
item["full_name"] = get_value("full_name")
- item["level"] = get_value("level")
item["state"] = get_value("state")
+ item["public_keys"] = map_key_params_to_dict(get_value("public_keys"))
objects.append(item)
return objects
@@ -332,13 +432,30 @@ def update_objects(want, have):
def main():
"""main entry point for module execution"""
+ public_key_spec = dict(
+ name=dict(required=True, type="str"),
+ key=dict(required=True, type="str", no_log=False),
+ type=dict(
+ required=True,
+ type="str",
+ choices=[
+ "ssh-dss",
+ "ssh-rsa",
+ "ecdsa-sha2-nistp256",
+ "ecdsa-sha2-nistp384",
+ "ssh-ed25519",
+ "ecdsa-sha2-nistp521",
+ ],
+ ),
+ )
element_spec = dict(
name=dict(),
full_name=dict(),
- level=dict(aliases=["role"]),
configured_password=dict(no_log=True),
+ encrypted_password=dict(no_log=False),
update_password=dict(default="always", choices=["on_create", "always"]),
state=dict(default="present", choices=["present", "absent"]),
+ public_keys=dict(type="list", elements="dict", options=public_key_spec),
)
aggregate_spec = deepcopy(element_spec)
@@ -359,7 +476,11 @@ def main():
argument_spec.update(element_spec)
- mutually_exclusive = [("name", "aggregate")]
+ mutually_exclusive = [
+ ("name", "aggregate"),
+ ("encrypted_password", "configured_password"),
+ ]
+
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
diff --git a/tests/integration/targets/vyos_smoke/tests/cli/caching.yaml b/tests/integration/targets/vyos_smoke/tests/cli/caching.yaml
index 9afea2e..b7e7e1f 100644
--- a/tests/integration/targets/vyos_smoke/tests/cli/caching.yaml
+++ b/tests/integration/targets/vyos_smoke/tests/cli/caching.yaml
@@ -6,11 +6,9 @@
interface_cmds:
- set interfaces ethernet eth1 description 'Configured by Ansible - Interface 1'
- set interfaces ethernet eth1 mtu '1500'
- - set interfaces ethernet eth1 duplex 'auto'
- - set interfaces ethernet eth1 speed 'auto'
- set interfaces ethernet eth1 vif 101 description 'Eth1 - VIF 101'
- set interfaces ethernet eth2 description 'Configured by Ansible - Interface 2 (ADMIN DOWN)'
- - set interfaces ethernet eth2 mtu '600'
+ - set interfaces ethernet eth2 mtu '1280'
l3_interface_cmds:
- set interfaces ethernet eth1 address '192.0.2.10/24'
- set interfaces ethernet eth1 address '2001:db8::10/32'
@@ -31,15 +29,13 @@
- name: eth1
description: Configured by Ansible - Interface 1
mtu: 1500
- speed: auto
- duplex: auto
vifs:
- vlan_id: 101
description: Eth1 - VIF 101
- name: eth2
description: Configured by Ansible - Interface 2 (ADMIN DOWN)
- mtu: 600
+ mtu: 1280
state: merged
- assert:
diff --git a/tests/integration/targets/vyos_user/tests/cli/auth.yaml b/tests/integration/targets/vyos_user/tests/cli/auth.yaml
index a3178bf..98a3d17 100644
--- a/tests/integration/targets/vyos_user/tests/cli/auth.yaml
+++ b/tests/integration/targets/vyos_user/tests/cli/auth.yaml
@@ -3,25 +3,24 @@
- name: Create user with password
vyos.vyos.vyos_user:
name: auth_user
- role: admin
state: present
configured_password: pass123
- name: test login via ssh with new user
expect:
command: >-
- ssh auth_user@{{ ansible_ssh_host }} -p {{ ansible_port | default(22) }} \
- -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no '/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper
- show version'
+ ssh auth_user@{{ ansible_ssh_host }} -p {{ ansible_port | default(22) }}
+ -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
+ '/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper show version'
responses:
(?i)password: pass123
- name: test login via ssh with invalid password (should fail)
expect:
command: >-
- ssh auth_user@{{ ansible_ssh_host }} -p {{ ansible_port | default(22) }} \
- -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no '/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper
- show version'
+ ssh auth_user@{{ ansible_ssh_host }} -p {{ ansible_port | default(22) }}
+ -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
+ '/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper show version'
responses:
(?i)password: badpass
ignore_errors: true
diff --git a/tests/integration/targets/vyos_user/tests/cli/basic.yaml b/tests/integration/targets/vyos_user/tests/cli/basic.yaml
index 096edc0..381f0db 100644
--- a/tests/integration/targets/vyos_user/tests/cli/basic.yaml
+++ b/tests/integration/targets/vyos_user/tests/cli/basic.yaml
@@ -26,15 +26,14 @@
vyos.vyos.vyos_user:
aggregate:
- name: ansibletest2
-
- name: ansibletest3
- level: operator
+ full_name: "test user"
state: present
- assert:
that:
- result.changed == true
- - result.commands == ["set system login user ansibletest2 level operator", "set system login user ansibletest3 level operator"]
+ - result.commands == ["set system login user ansibletest2 full-name 'test user'", "set system login user ansibletest3 full-name 'test user'"]
- name: Add user again (Idempotent)
register: result
@@ -56,7 +55,6 @@
- name: ansibletest2
- name: ansibletest3
- level: operator
state: present
- assert:
diff --git a/tests/integration/targets/vyos_user/tests/cli/encrypted.yaml b/tests/integration/targets/vyos_user/tests/cli/encrypted.yaml
new file mode 100644
index 0000000..39fbf61
--- /dev/null
+++ b/tests/integration/targets/vyos_user/tests/cli/encrypted.yaml
@@ -0,0 +1,97 @@
+---
+- debug: msg="START cli/basic.yaml on connection={{ ansible_connection }}"
+
+- name: Setup
+ vyos.vyos.vyos_config:
+ lines:
+ - delete system login user ansibletest1
+ - delete system login user ansibletest2
+ - delete system login user ansibletest3
+
+- name: Create user
+ register: result
+ vyos.vyos.vyos_user:
+ name: ansibletest1
+ encrypted_password: "{{ encrypted_password }}"
+ state: present
+
+- assert:
+ that:
+ - result.changed == true
+ - "{{ encrypted_add['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
+
+- name: Collection of users (SetUp)
+ register: result
+ vyos.vyos.vyos_user:
+ aggregate:
+ - name: ansibletest2
+ - name: ansibletest3
+ full_name: "test user"
+ encrypted_password: "{{ encrypted_password }}"
+ state: present
+
+- assert:
+ that:
+ - result.changed == true
+ - "{{ encrypted_aggregate_add['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
+
+- name: Add user again (Idempotent)
+ register: result
+ vyos.vyos.vyos_user:
+ name: ansibletest1
+ encrypted_password: "{{ encrypted_password }}"
+ state: present
+
+- assert:
+ that:
+ - result.changed == false
+ - result.commands | length == 0
+
+- name: Add collection of users (Idempotent)
+ register: result
+ vyos.vyos.vyos_user:
+ aggregate:
+ - name: ansibletest2
+ - name: ansibletest3
+ encrypted_password: "{{ encrypted_password }}"
+ state: present
+
+- name: Change user password
+ register: result
+ vyos.vyos.vyos_user:
+ name: ansibletest1
+ encrypted_password: "{{ encrypted_password_2 }}"
+ state: present
+
+- assert:
+ that:
+ - result.changed == true
+ - "{{ encrypted_change['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
+
+- name: Change collection of users
+ register: result
+ vyos.vyos.vyos_user:
+ aggregate:
+ - name: ansibletest2
+ - name: ansibletest3
+ encrypted_password: "{{ encrypted_password_2 }}"
+ state: present
+
+- assert:
+ that:
+ - result.changed == true
+ - "{{ encrypted_aggregate_change['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
+
+- name: tearDown
+ register: result
+ vyos.vyos.vyos_user:
+ users:
+ - name: ansibletest1
+ - name: ansibletest2
+ - name: ansibletest3
+ state: absent
+
+- assert:
+ that:
+ - result.changed == true
+ - result.commands == ["delete system login user ansibletest1", "delete system login user ansibletest2", "delete system login user ansibletest3"]
diff --git a/tests/integration/targets/vyos_user/tests/cli/public_keys.yaml b/tests/integration/targets/vyos_user/tests/cli/public_keys.yaml
new file mode 100644
index 0000000..9ffa41e
--- /dev/null
+++ b/tests/integration/targets/vyos_user/tests/cli/public_keys.yaml
@@ -0,0 +1,129 @@
+---
+- debug: msg="START cli/basic.yaml on connection={{ ansible_connection }}"
+
+- name: Setup
+ vyos.vyos.vyos_config:
+ lines:
+ - delete system login user ssh_test_1
+ - delete system login user ssh_test_2
+ - delete system login user ssh_test_3
+
+- name: Create first user
+ register: result
+ vyos.vyos.vyos_user:
+ name: ssh_test_1
+ public_keys:
+ - name: test_key
+ key: "AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu"
+ type: ssh-ed25519
+ state: present
+
+- debug:
+ var: result
+- debug:
+ var: ssh_add['commands']
+
+- assert:
+ that:
+ - result.changed == true
+ - "{{ ssh_add['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
+
+- name: Collection of users (SetUp)
+ register: result
+ vyos.vyos.vyos_user:
+ aggregate:
+ - name: ssh_test_2
+ - name: ssh_test_3
+ full_name: "test user"
+ public_keys:
+ - name: test_key_2
+ key: "AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu"
+ type: ssh-ed25519
+ state: present
+
+- debug:
+ var: result
+- debug:
+ var: ssh_aggregate_add['commands']
+
+- assert:
+ that:
+ - result.changed == true
+ - "{{ ssh_aggregate_add['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
+
+- name: Add user again (Idempotent)
+ register: result
+ vyos.vyos.vyos_user:
+ name: ssh_test_1
+ public_keys:
+ - name: test_key
+ key: "AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu"
+ type: ssh-ed25519
+ state: present
+
+- assert:
+ that:
+ - result.changed == false
+ - result.commands | length == 0
+
+- name: Add collection of users (Idempotent)
+ register: result
+ vyos.vyos.vyos_user:
+ aggregate:
+ - name: ssh_test_2
+ - name: ssh_test_3
+ public_keys:
+ - name: test_key_2
+ key: "AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu"
+ type: ssh-ed25519
+ state: present
+
+- assert:
+ that:
+ - result.changed == false
+ - result.commands | length == 0
+
+- name: Change user key
+ register: result
+ vyos.vyos.vyos_user:
+ name: ssh_test_1
+ public_keys:
+ - name: test_key_3
+ key: "AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu"
+ type: ssh-ed25519
+ state: present
+
+- assert:
+ that:
+ - result.changed == True
+ - "{{ ssh_change_key['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
+
+- name: change collection of users keys
+ register: result
+ vyos.vyos.vyos_user:
+ aggregate:
+ - name: ssh_test_2
+ - name: ssh_test_3
+ public_keys:
+ - name: test_key_4
+ key: "AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu"
+ type: ssh-ed25519
+ state: present
+- assert:
+ that:
+ - result.changed == True
+ - "{{ ssh_aggregate_change['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
+
+- name: tearDown
+ register: result
+ vyos.vyos.vyos_user:
+ users:
+ - name: ssh_test_1
+ - name: ssh_test_2
+ - name: ssh_test_3
+ state: absent
+
+- assert:
+ that:
+ - result.changed == true
+ - result.commands == ["delete system login user ssh_test_1", "delete system login user ssh_test_2", "delete system login user ssh_test_3"]
diff --git a/tests/integration/targets/vyos_user/vars/main.yaml b/tests/integration/targets/vyos_user/vars/main.yaml
new file mode 100644
index 0000000..89163fa
--- /dev/null
+++ b/tests/integration/targets/vyos_user/vars/main.yaml
@@ -0,0 +1,64 @@
+---
+ssh_add:
+ commands:
+ - set system login user ssh_test_1 authentication public-keys test_key key 'AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu'
+ - set system login user ssh_test_1 authentication public-keys test_key type 'ssh-ed25519'
+
+ssh_aggregate_add:
+ commands:
+ - set system login user ssh_test_2 full-name 'test user'
+ - set system login user ssh_test_2 authentication public-keys test_key_2 key 'AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu'
+ - set system login user ssh_test_2 authentication public-keys test_key_2 type 'ssh-ed25519'
+ - set system login user ssh_test_3 full-name 'test user'
+ - set system login user ssh_test_3 authentication public-keys test_key_2 key 'AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu'
+ - set system login user ssh_test_3 authentication public-keys test_key_2 type 'ssh-ed25519'
+
+ssh_change_key:
+ commands:
+ - delete system login user ssh_test_1 authentication public-keys test_key
+ - set system login user ssh_test_1 authentication public-keys test_key_3 key 'AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu'
+ - set system login user ssh_test_1 authentication public-keys test_key_3 type 'ssh-ed25519'
+
+ssh_aggregate_change:
+ commands:
+ - delete system login user ssh_test_2 authentication public-keys test_key_2
+ - set system login user ssh_test_2 authentication public-keys test_key_4 key 'AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu'
+ - set system login user ssh_test_2 authentication public-keys test_key_4 type 'ssh-ed25519'
+ - delete system login user ssh_test_3 authentication public-keys test_key_2
+ - set system login user ssh_test_3 authentication public-keys test_key_4 key 'AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu'
+ - set system login user ssh_test_3 authentication public-keys test_key_4 type 'ssh-ed25519'
+
+encrypted_password: "$6$x6SQ/zSxNwIEuqnL$hHmU/NXfAK/pFWXoCi91kKPAiQtf/cyckOlBUDUIL44QOUZHnqipHtz2znwYHQVM0Lqm6aFnm7Qs9WFlRf4mW/"
+
+encrypted_add:
+ commands:
+ - >-
+ set system login user ansibletest1 authentication encrypted-password
+ '$6$x6SQ/zSxNwIEuqnL$hHmU/NXfAK/pFWXoCi91kKPAiQtf/cyckOlBUDUIL44QOUZHnqipHtz2znwYHQVM0Lqm6aFnm7Qs9WFlRf4mW/'
+encrypted_aggregate_add:
+ commands:
+ - set system login user ansibletest2 full-name 'test user'
+ - >-
+ set system login user ansibletest2 authentication encrypted-password
+ '$6$x6SQ/zSxNwIEuqnL$hHmU/NXfAK/pFWXoCi91kKPAiQtf/cyckOlBUDUIL44QOUZHnqipHtz2znwYHQVM0Lqm6aFnm7Qs9WFlRf4mW/'
+ - set system login user ansibletest3 full-name 'test user'
+ - >-
+ set system login user ansibletest3 authentication encrypted-password
+ '$6$x6SQ/zSxNwIEuqnL$hHmU/NXfAK/pFWXoCi91kKPAiQtf/cyckOlBUDUIL44QOUZHnqipHtz2znwYHQVM0Lqm6aFnm7Qs9WFlRf4mW/'
+
+encrypted_password_2: "$6$drNuMGFEgJ6Vremv$ukdc1trPwatKTUFVA9J1rAJsoWU.9ssgyZBoM7/ReK/yVAcxGbwx7.7VrKwF.Bag.thXXoXSduLtTzTlcJnU6."
+
+encrypted_change:
+ commands:
+ - >-
+ set system login user ansibletest1 authentication encrypted-password
+ '$6$drNuMGFEgJ6Vremv$ukdc1trPwatKTUFVA9J1rAJsoWU.9ssgyZBoM7/ReK/yVAcxGbwx7.7VrKwF.Bag.thXXoXSduLtTzTlcJnU6.'
+
+encrypted_aggregate_change:
+ commands:
+ - >-
+ set system login user ansibletest2 authentication encrypted-password
+ '$6$drNuMGFEgJ6Vremv$ukdc1trPwatKTUFVA9J1rAJsoWU.9ssgyZBoM7/ReK/yVAcxGbwx7.7VrKwF.Bag.thXXoXSduLtTzTlcJnU6.'
+ - >-
+ set system login user ansibletest3 authentication encrypted-password
+ '$6$drNuMGFEgJ6Vremv$ukdc1trPwatKTUFVA9J1rAJsoWU.9ssgyZBoM7/ReK/yVAcxGbwx7.7VrKwF.Bag.thXXoXSduLtTzTlcJnU6.'
diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_user_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_user_config.cfg
index 81cd1a4..9b73106 100644
--- a/tests/unit/modules/network/vyos/fixtures/vyos_user_config.cfg
+++ b/tests/unit/modules/network/vyos/fixtures/vyos_user_config.cfg
@@ -1,2 +1,4 @@
-set system login user admin level operator authentication encrypted-password '$6$V5oWW3JM9NFAwOG$P2L4raFvIrZjjs3g0qmH4Ns5ti7flRpSs6aEqy4TrGZYXGeBiYzwi2A6jy'
-set system login user ansible level operator authentication encrypted-password '$6$ZfvSv6A50W6yNPYX$4HP5eg2sywcXYxTqhApQ7zvUvx0HsQHrI9xuJoFLy2gM/'
+set system login user admin authentication encrypted-password '$6$V5oWW3JM9NFAwOG$P2L4raFvIrZjjs3g0qmH4Ns5ti7flRpSs6aEqy4TrGZYXGeBiYzwi2A6jy'
+set system login user ansible authentication encrypted-password '$6$ZfvSv6A50W6yNPYX$4HP5eg2sywcXYxTqhApQ7zvUvx0HsQHrI9xuJoFLy2gM/'
+set system login user ssh authentication public-keys user@host key 'AAAAB3NzaC1yc2EAAAADAQABAAABAQD'
+set system login user ssh authentication public-keys user@host type 'ssh-rsa'
diff --git a/tests/unit/modules/network/vyos/test_vyos_user.py b/tests/unit/modules/network/vyos/test_vyos_user.py
index 7029720..e8c5078 100644
--- a/tests/unit/modules/network/vyos/test_vyos_user.py
+++ b/tests/unit/modules/network/vyos/test_vyos_user.py
@@ -67,18 +67,6 @@ class TestVyosUserModule(TestVyosModule):
result = self.execute_module(changed=True)
self.assertEqual(result["commands"], ["delete system login user ansible"])
- def test_vyos_user_level(self):
- set_module_args(dict(name="ansible", level="operator"))
- result = self.execute_module(changed=True)
- self.assertEqual(
- result["commands"],
- ["set system login user ansible level operator"],
- )
-
- def test_vyos_user_level_invalid(self):
- set_module_args(dict(name="ansible", level="sysadmin"))
- self.execute_module(failed=True)
-
def test_vyos_user_purge(self):
set_module_args(dict(purge=True))
result = self.execute_module(changed=True)
@@ -88,6 +76,7 @@ class TestVyosUserModule(TestVyosModule):
[
"delete system login user ansible",
"delete system login user admin",
+ "delete system login user ssh",
],
),
)
@@ -129,3 +118,122 @@ class TestVyosUserModule(TestVyosModule):
result["commands"],
["set system login user ansible authentication plaintext-password test"],
)
+
+ def test_vyos_user_set_ssh_key(self):
+ set_module_args(
+ dict(
+ name="ansible",
+ public_keys=[
+ dict(
+ name="user@host",
+ key="AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu",
+ type="ssh-ed25519",
+ ),
+ ],
+ ),
+ )
+ result = self.execute_module(changed=True)
+ self.assertEqual(
+ result["commands"],
+ [
+ "set system login user ansible authentication public-keys user@host key 'AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu'",
+ "set system login user ansible authentication public-keys user@host type 'ssh-ed25519'",
+ ],
+ )
+
+ def test_vyos_user_set_ssh_key_idempotent(self):
+ set_module_args(
+ dict(
+ name="ssh",
+ public_keys=[
+ dict(
+ name="user@host",
+ key="AAAAB3NzaC1yc2EAAAADAQABAAABAQD",
+ type="ssh-rsa",
+ ),
+ ],
+ ),
+ )
+ self.load_fixtures()
+ result = self.execute_module(changed=False)
+ self.assertEqual(result["commands"], [])
+
+ def test_vyos_user_set_ssh_key_change(self):
+ set_module_args(
+ dict(
+ name="ssh",
+ public_keys=[
+ dict(
+ name="user@host",
+ key="AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu",
+ type="ssh-ed25519",
+ ),
+ ],
+ ),
+ )
+ self.load_fixtures()
+ result = self.execute_module(
+ changed=True,
+ commands=[
+ "set system login user ssh authentication public-keys user@host key 'AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu'",
+ "set system login user ssh authentication public-keys user@host type 'ssh-ed25519'",
+ ],
+ )
+
+ def test_vyos_user_set_ssh_key_add_and_remove(self):
+ set_module_args(
+ dict(
+ name="ssh",
+ public_keys=[
+ dict(
+ name="noone@nowhere",
+ key="AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu",
+ type="ssh-ed25519",
+ ),
+ ],
+ ),
+ )
+ self.load_fixtures()
+ result = self.execute_module(
+ changed=True,
+ commands=[
+ "delete system login user ssh authentication public-keys user@host",
+ "set system login user ssh authentication public-keys noone@nowhere key 'AAAAC3NzaC1lZDI1NTE5AAAAIFIR0jrMvBdmvTJNY5EDhOD+eixvbOinhY1eBU2uyuhu'",
+ "set system login user ssh authentication public-keys noone@nowhere type 'ssh-ed25519'",
+ ],
+ )
+
+ def test_vyos_user_set_ssh_key_empty(self):
+ # empty public_keys has no effect (for setting passwords, user names, etc.)
+ set_module_args(
+ dict(
+ name="ssh",
+ public_keys=[],
+ ),
+ )
+ self.load_fixtures()
+ result = self.execute_module(changed=False)
+
+ def test_vyos_user_set_encrypted_password(self):
+ set_module_args(
+ dict(
+ name="ansible",
+ encrypted_password="$6$rounds=656000$SALT$HASH",
+ ),
+ )
+ result = self.execute_module(changed=True)
+ self.assertEqual(
+ result["commands"],
+ [
+ "set system login user ansible authentication encrypted-password '$6$rounds=656000$SALT$HASH'",
+ ],
+ )
+
+ def test_vyos_user_set_encrypted_password_idem(self):
+ set_module_args(
+ dict(
+ name="ansible",
+ encrypted_password="$6$ZfvSv6A50W6yNPYX$4HP5eg2sywcXYxTqhApQ7zvUvx0HsQHrI9xuJoFLy2gM/",
+ ),
+ )
+ result = self.execute_module(changed=False)