New passkeyconfig management module

There is a new paskeyconfig management module placed in the plugins
folder:

    plugins/modules/ipapasskeyconfig.py

The paskeyconfig module allows to retrieve and modify global passkey
configuration attributes.

Here is the documentation of the module:

    README-passkeyconfig.md

New example playbooks have been added:

    playbooks/passkeyconfig/passkeyconfig-retrieve.yml
    playbooks/passkeyconfig/passkeyconfig-present.yml

New tests for the module can be found at:

    tests/passkeyconfig/test_passkeyconfig.yml
    tests/passkeyconfig/test_passkeyconfig_client_context.yml

Signed-off-by: Rafael Guterres Jeffman <rjeffman@redhat.com>
This commit is contained in:
Rafael Guterres Jeffman
2025-07-07 15:50:57 -03:00
parent 536b7cb5f3
commit bf384ab1aa
7 changed files with 395 additions and 0 deletions

88
README-passkeyconfig.md Normal file
View File

@@ -0,0 +1,88 @@
Passkeyconfig module
============
Description
-----------
The passkeyconfig module allows to manage FreeIPA passkey configuration settings.
Features
--------
* Passkeyconfig management
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipapasskeyconfig module.
Requirements
------------
**Controller**
* Ansible version: 2.15+
**Node**
* Supported FreeIPA version (see above)
Usage
=====
Example inventory file
```ini
[ipaserver]
ipaserver.test.local
```
By default, user verification for passkey authentication is turned on (`true`). Example playbook to ensure that the requirement for user verification for passkey authentication is turned off:
```yaml
---
- name: Playbook to manage IPA passkeyconfig.
hosts: ipaserver
become: false
tasks:
- name: Ensure require_user_verification is false
ipapasskeyconfig:
ipaadmin_password: SomeADMINpassword
require_user_verification: false
```
Example playbook to get current passkeyconfig:
```yaml
---
- name: Playbook to get IPA passkeyconfig.
hosts: ipaserver
become: false
tasks:
- name: Retrieve current passkey configuration
ipapasskeyconfig:
ipaadmin_password: SomeADMINpassword
```
Variables
---------
Variable | Description | Required
-------- | ----------- | --------
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
`ipaapi_ldap_cache` | Use LDAP cache for IPA connection. The bool setting defaults to true. (bool) | no
`require_user_verification` \| `iparequireuserverification` | Require user verification for passkey authentication. (bool) | no
Authors
=======
Rafael Guterres Jeffman

View File

@@ -38,6 +38,7 @@ Features
* Modules for idview management * Modules for idview management
* Modules for location management * Modules for location management
* Modules for netgroup management * Modules for netgroup management
* Modules for passkeyconfig management
* Modules for permission management * Modules for permission management
* Modules for privilege management * Modules for privilege management
* Modules for pwpolicy management * Modules for pwpolicy management
@@ -454,6 +455,7 @@ Modules in plugin/modules
* [idview](README-idview.md) * [idview](README-idview.md)
* [ipalocation](README-location.md) * [ipalocation](README-location.md)
* [ipanetgroup](README-netgroup.md) * [ipanetgroup](README-netgroup.md)
* [ipapasskeyconfig](README-passkeyconfig.md)
* [ipapermission](README-permission.md) * [ipapermission](README-permission.md)
* [ipaprivilege](README-privilege.md) * [ipaprivilege](README-privilege.md)
* [ipapwpolicy](README-pwpolicy.md) * [ipapwpolicy](README-pwpolicy.md)

View File

@@ -0,0 +1,10 @@
---
- name: Passkeyconfig example
hosts: ipaserver
become: no
tasks:
- name: Set passkeyconfig require_user_verification to false
ipapasskeyconfig:
ipaadmin_password: SomeADMINpassword
require_user_verification: false

View File

@@ -0,0 +1,14 @@
---
- name: Passkeyconfig get current configuration example
hosts: ipaserver
become: true
tasks:
- name: Get current passkey configuration
ipapasskeyconfig:
ipaadmin_password: SomeADMINpassword
register: result
- name: Display current passkey configuration
ansible.builtin.debug:
var: result.passkeyconfig

View File

@@ -0,0 +1,174 @@
# -*- coding: utf-8 -*-
# Authors:
# Rafael Guterres Jeffman <rjeffman@redhat.com>
#
# Copyright (C) 2025 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.0",
"supported_by": "community",
"status": ["preview"],
}
DOCUMENTATION = """
---
module: ipapasskeyconfig
short_description: Manage FreeIPA passkeyconfig
description: Manage FreeIPA passkeyconfig
extends_documentation_fragment:
- ipamodule_base_docs
options:
require_user_verification:
description: Require user verification for passkey authentication
required: false
type: bool
default: true
aliases: ["iparequireuserverification"]
author:
- Rafael Guterres Jeffman (@rjeffman)
"""
EXAMPLES = """
# Set passkeyconfig
- ipapasskeyconfig:
ipaadmin_password: SomeADMINpassword
require_user_verification: false
# Get current passkeyconfig
- ipapasskeyconfig:
ipaadmin_password: SomeADMINpassword
"""
RETURN = """
passkeyconfig:
description: Dict of passkeyconfig settings
returned: always
type: dict
contains:
require_user_verification:
description: Require user verification for passkey authentication
type: bool
returned: always
"""
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, ipalib_errors
from ansible.module_utils import six
if six.PY3:
unicode = str
def find_passkeyconfig(module):
"""Find the current passkeyconfig settings."""
try:
_result = module.ipa_command_no_name(
"passkeyconfig_show", {"all": True})
except ipalib_errors.NotFound:
# An exception is raised if passkeyconfig is not found.
return None
return _result["result"]
def gen_args(require_user_verification):
_args = {}
if require_user_verification is not None:
_args["iparequireuserverification"] = require_user_verification
return _args
def main():
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# passkeyconfig
require_user_verification=dict(
required=False, type='bool',
aliases=["iparequireuserverification"],
default=None
),
),
supports_check_mode=True,
)
ansible_module._ansible_debug = True
# Get parameters
require_user_verification = (
ansible_module.params_get("require_user_verification")
)
# Init
changed = False
exit_args = {}
# Connect to IPA API
with ansible_module.ipa_connect():
if not ansible_module.ipa_command_exists("passkeyconfig_show"):
msg = "Managing passkeyconfig is not supported by your IPA version"
ansible_module.fail_json(msg=msg)
result = find_passkeyconfig(ansible_module)
if result is None:
ansible_module.fail_json(msg="Could not retrieve passkeyconfig")
if require_user_verification is not None:
# Generate args
args = gen_args(require_user_verification)
# Check if there are different settings in the find result.
# If yes: modify
if not compare_args_ipa(ansible_module, args, result):
changed = True
if not ansible_module.check_mode:
try:
ansible_module.ipa_command_no_name(
"passkeyconfig_mod", args)
except ipalib_errors.EmptyModlist:
changed = False
except Exception as e:
ansible_module.fail_json(
msg="passkeyconfig_mod failed: %s" % str(e))
else:
# No parameters provided, just return current config
pass
# Get updated config if changes were made
if changed:
result = find_passkeyconfig(ansible_module)
# Prepare exit args
exit_args["passkeyconfig"] = {}
if result:
# Map IPA API field to module parameter
if "iparequireuserverification" in result:
exit_args["passkeyconfig"]["require_user_verification"] = \
result["iparequireuserverification"][0]
# Done
ansible_module.exit_json(changed=changed, **exit_args)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,67 @@
---
- name: Test passkeyconfig
hosts: "{{ ipa_test_host | default('ipaserver') }}"
# It is normally not needed to set "become" to "true" for a module test.
# Only set it to true if it is needed to execute commands as root.
become: false
# Enable "gather_facts" only if "ansible_facts" variable needs to be used.
gather_facts: false
module_defaults:
ipapasskeyconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
tasks:
- name: Include FreeIPA facts.
ansible.builtin.include_tasks: ../env_freeipa_facts.yml
- name: Run tests only if passkey is supported
when: passkey_is_supported
block:
# TESTS
- name: Get current passkeyconfig
ipapasskeyconfig:
register: result_initial
failed_when: result_initial.failed
- name: Ensure require_user_verification is set to false
ipapasskeyconfig:
require_user_verification: false
register: result
failed_when: result.failed
- name: Ensure require_user_verification is set to false again
ipapasskeyconfig:
require_user_verification: false
register: result
failed_when: result.changed or result.failed
- name: Verify require_user_verification is false
ansible.builtin.assert:
that:
- result.passkeyconfig.require_user_verification == false
- name: Ensure require_user_verification is set to true
ipapasskeyconfig:
require_user_verification: true
register: result
failed_when: not result.changed or result.failed
- name: Ensure require_user_verification is set to true again
ipapasskeyconfig:
require_user_verification: true
register: result
failed_when: result.changed or result.failed
- name: Verify require_user_verification is true
ansible.builtin.assert:
that:
- result.passkeyconfig.require_user_verification == true
# CLEANUP: Restore original configuration
- name: Restore original passkeyconfig
ipapasskeyconfig:
require_user_verification: "{{ result_initial.passkeyconfig.require_user_verification }}"
when: result_initial.passkeyconfig is defined and result_initial.passkeyconfig.require_user_verification is defined

View File

@@ -0,0 +1,40 @@
---
- name: Test passkeyconfig
hosts: ipaclients, ipaserver
# It is normally not needed to set "become" to "true" for a module test.
# Only set it to true if it is needed to execute commands as root.
become: false
# Enable "gather_facts" only if "ansible_facts" variable needs to be used.
gather_facts: false
tasks:
- name: Include FreeIPA facts.
ansible.builtin.include_tasks: ../env_freeipa_facts.yml
# Test will only be executed if host is not a server.
- name: Execute with server context in the client.
ipapasskeyconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: server
require_user_verification: false
register: result
failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
when: ipa_host_is_client and passkey_is_supported
# Import basic module tests, and execute with ipa_context set to 'client'.
# If ipaclients is set, it will be executed using the client, if not,
# ipaserver will be used.
#
# With this setup, tests can be executed against an IPA client, against
# an IPA server using "client" context, and ensure that tests are executed
# in upstream CI.
- name: Test passkeyconfig using client context, in client host.
import_playbook: test_passkeyconfig.yml
when: groups['ipaclients'] and passkey_is_supported
vars:
ipa_test_host: ipaclients
- name: Test passkeyconfig using client context, in server host.
import_playbook: test_passkeyconfig.yml
when: passkey_is_supported and (groups['ipaclients'] is not defined or not groups['ipaclients'])