Compare commits

...

107 Commits

Author SHA1 Message Date
Thomas Woerner
1930d8c8be Merge pull request #1168 from rjeffman/dev_reproduce_upstream_groups
Reproduce upstream CI groups in developer's machine
2023-11-24 15:39:47 +01:00
Thomas Woerner
1837ee662c Merge pull request #1104 from rjeffman/ci_parallel_image_build
upstream CI: Build containers in parallel jobs
2023-11-24 14:50:44 +01:00
Thomas Woerner
26e171df79 Merge pull request #1170 from rjeffman/ci_ansible_future
Update ansible-lint and pylint versions
2023-11-24 14:46:22 +01:00
Thomas Woerner
01440e3c04 Merge pull request #1173 from rjeffman/ipahost_fix_dnsrecords
ipahost: Remove dangling dns records during test setup
2023-11-24 14:44:19 +01:00
Rafael Guterres Jeffman
2426e04c22 Merge pull request #1176 from t-woerner/idoverrideX_del_without_delete_continue
idoverride{user,group}: Fix delete_continue with state absent
2023-11-24 09:02:05 -03:00
Thomas Woerner
92e44f6a6c idoverride{user,group}: Fix delete_continue with state absent
All tasks for idoverrideuser and idoverridegroup with state absent
failed with "'continue' is required" when delete_continue was not set.

This happended as delete_continue was internally None and continue: None
was provided to the API.

The fix is simply to use '"continue": delete_continue or False' so that
continue is set to False in this case.
2023-11-16 13:47:04 +01:00
Rafael Guterres Jeffman
16c8ee87e9 ipahost: Remove dangling dns records during test setup
When testing ipahost through the test playbooks, if there are previous
DNS A/AAAA records, the test fails due to a false positive idempotence
issue.

This patch ensures that all DNS records for the test hosts are absent
before test execution.

This issue could be seen in the 2023-11-06 Azure Nightly pipeline
execution.
2023-11-10 20:26:51 -03:00
Rafael Guterres Jeffman
3109e9d1bc utils/run-tests.sh: Replicate Azure's test grouping
When running ansible-freeipa's Azure pipelines for nightly and weekly
tests, due to the amount of tests to execute, tests are grouped and
executed in parallel jobs.

Due to a still unkonwn issue, depending on the order the tests are
executed, some random failures may occur and debugging them is hard due
to current implementation of the tests.

This patch adds support for replicating the tests of a specific Azure
test group once the seed used to create groups and the group number are
provided, allowing the test failures to be replicated on the developer's
workstation where it can be more easily debugged.

A new option is added to 'utils/run-tests.sh', '-A SEED.G' that is used
to define the seed and group to replicate the tests. The seed is a date,
with the format "YYYYMMDD", so, for example '-A 20230611.2' would
execute the same tests, in the same order as the second group of tests
for date 2023-06-11. To aid in usability 'YYYY-MM-DD' may also be used.

When using '-A' neither '-s' (test suites) or specific tests (positional
arguments) can be used.

Also, to help fixing tests, an option to stop the tests on the first
test failure ('-x') was added to the script.
2023-11-09 12:36:59 -03:00
Rafael Guterres Jeffman
b457de545d Update ansible-lint and pylint versions
ansible-lint 6.21+ and pylint 3.0+ will be required for Ansible
collections to be approved on Ansible Galaxy.

This patch updates pre-commit and upstream linters to use the required
versions.
2023-11-08 15:11:02 -03:00
Rafael Guterres Jeffman
f1a6f44477 Merge pull request #1158 from t-woerner/idview_fail_to_apply_invalid_hosts
ipaidview: Fail to apply unknown (invalid) hosts
2023-10-22 22:43:03 -03:00
Rafael Guterres Jeffman
1dbe19cefb Merge pull request #1156 from t-woerner/hbacsvcgroup_remove_oobsolete_result_handler
hbacsvcgroup: Remove obsolete result_handler
2023-10-22 22:42:06 -03:00
Rafael Guterres Jeffman
7982fad342 Merge pull request #1155 from t-woerner/hbacrule_with_svcgroup_Sudo
hbacrule: Fix use of builtin sudo hbacsvcgroup
2023-10-22 22:41:18 -03:00
Thomas Woerner
212719496c ipaidview: Fail to apply unknown (invalid) hosts
The task to apply an unknown (invalid) host to an idview was not failing
as expected and only reported no change.

A new host verification step has been added to fail before trying to
apply invalid hosts. unapplying an invalid host is not failing as the
invalid host is indeed not applied.
2023-10-21 01:10:13 +02:00
Thomas Woerner
3de6f9146e hbacsvcgroup: Remove obsolete result_handler
The result_handler is not needed anymore as the idempotency issues with
members have been fixed already for this module.

Related: #685 hbacsvcgroup: Fix member management idempotence issues.
2023-10-21 01:09:21 +02:00
Thomas Woerner
48f2ef88a4 hbacrule: Fix use of builtin sudo hbacsvcgroup
hbacsvcgroup names are converted to lower case while creation with
hbacsvcgroup_add.

The hbacsvcgroup for sudo is builtin with the name "Sudo" though. This
breaks the lower case comparison. Therefore all memberservice_hbacsvcgroup
items are converted to lower case if "Sudo" is in the list.
2023-10-21 01:08:44 +02:00
Rafael Guterres Jeffman
6845acd596 upstream CI: Build containers in parallel jobs
In the current build container pipeline, all steps are serialized in a
single job, and if one of the jobs fail to build, due to broken
dependent image, or some Azure glitch, like slow connection, the only
way to rebuild the failed container is to rebuild all containers.

By building containers in parallel jobs, if a container fails to build
it is possible to restart only the failed job.
2023-10-20 13:44:09 -03:00
Thomas Woerner
ba7bf0f6cd Merge pull request #1148 from rjeffman/fix_checkpr_test_selection
upstream CI: Fix test selection for CheckPR pipeline.
2023-10-20 16:35:03 +02:00
Rafael Guterres Jeffman
fe2d17e4df upstream ci: Run PR tests using a single job.
The usual scenario for PR checks is to execute only a few tests, and
searching for the results in several jobs makes it harder to find
issues.

By using a single job run the tests would take some more time to
complete, although not much, as only a small subset is executed, and
test verification would be easier and less error prone.
2023-10-19 09:41:55 -03:00
Rafael Guterres Jeffman
319a0d3d86 upstream ci: Use a single random seed for spliting tests
Dependind on how long it took for the jobs to start, a different seed
would be used to group tests and tests could either repeat or not be
selected at all.

By using a seed based on the day the test run reduces the chance of
using different random seeds, and still allow for the tests to be
executed in a different order.

The execution in different order is important to identify tests that
work or fail only if executed after other tests.
2023-10-19 09:41:55 -03:00
Rafael Guterres Jeffman
c71a2b33dd upstream CI: Fix test selection for CheckPR pipeline.
Due to an error on processing Ansible key 'import_tasks' the script that
creates a list of modules to test is broken making some modules to be
not tested.

By fixing the handling of 'import_tasks' and module import, the list is
correct again and the list of modules to be tested now include the ones
which depend on the modified module.
2023-10-19 09:41:55 -03:00
Thomas Woerner
02223dfb67 Merge pull request #1159 from rjeffman/ci_pin_ansible_lint
upstream CI: Pin ansible-lint version to 6.20 series
2023-10-19 13:18:27 +02:00
Rafael Guterres Jeffman
5731a1539b upstream CI: Pin ansible-lint version to 6.20 series
The release version 6.21.0 of ansible-lint introduced a bug that breaks
the reporting of 'warning' messages. [1]

This patch pins ansible-lint version to the latest one in the 6.20
series, so that it can still be used to check pull requests.

[1]: https://github.com/ansible/ansible-lint/issues/3853
2023-10-18 15:36:16 -03:00
Thomas Woerner
ee7354230b Merge pull request #1157 from rjeffman/ci_fix_docker_install
upstream CI: Pin Python version to 3.11
2023-10-18 16:14:49 +02:00
Rafael Guterres Jeffman
4bb40f3397 Merge pull request #1146 from t-woerner/update_ansible-freeipa.spec.in_for_doverridegroup
utils/ansible-freeipa.spec.in: Add ref for idoverridegroup management
2023-10-17 17:05:39 -03:00
Rafael Guterres Jeffman
55b8729c52 upstream CI: Pin Python version to 3.11
Azure Ubuntu images have Python 3.12 available, and as we did not pin
the requested Python version, the latest available one was used, causing
image preparation and tests to fail.

This patch pins Python version to 3.11 until test can be executed with
Python 3.12 and later.
2023-10-17 16:32:37 -03:00
Rafael Guterres Jeffman
539ace413d Merge pull request #1105 from t-woerner/new_idp_module
New idp management module
2023-09-27 12:12:14 -03:00
Thomas Woerner
0c20b34d28 utils/ansible-freeipa.spec.in: Add ref for idoverridegroup management
The idoverridegroup management reference has been added to the
description.
2023-09-27 14:43:11 +02:00
Thomas Woerner
f9ff41320f New idp management module
There is a new idp management module placed in the plugins folder:

    plugins/modules/ipaidp.py

The idp module allows to ensure presence or absence of external Identity
Providers.

Here is the documentation for the module:

    README-idp.md

New idp example playbooks:

    playbooks/idp/idp-present.yml
    playbooks/idp/idp-absent.yml

New tests for the module:

    tests/idp/test_idp.yml
    tests/idp/test_idp_client_context.yml
2023-09-27 10:52:55 +02:00
Thomas Woerner
69c6b4d644 Merge pull request #1145 from rjeffman/revert_ansible_2_9
Revert "upstream ci: Run nightly tests against Ansible 2.9"
2023-09-26 17:41:08 +02:00
Rafael Guterres Jeffman
b63716b724 Revert "upstream ci: Run nightly tests against Ansible 2.9"
Most of our usptream CI test imagens do not handle Ansible 2.9 so, this
cange is being reverted.

This reverts commit 34654d1090.
2023-09-26 11:25:58 -03:00
Thomas Woerner
3cf138674b Merge pull request #1144 from rjeffman/upstream_ci_ansible_2_9
Ensure CI runs against the oldest supported Ansible versions.
2023-09-25 16:04:59 +02:00
Thomas Woerner
12e0d110f6 Merge pull request #1112 from rjeffman/future_pylint
Bump linter versions.
2023-09-25 16:02:53 +02:00
Rafael Guterres Jeffman
34654d1090 upstream ci: Run nightly tests against Ansible 2.9
Recently it was announced that Ansible 2.9 will be supported for some
time, and this patch ensures that we run the nightly tests against this
version of Ansible.
2023-09-19 15:54:38 -03:00
Rafael Guterres Jeffman
72d3ab8e04 upstream ci: Run PR checks against the oldest supported ansible-core
Recently, a change in the deployment roles forced the change to the
minimum version of ansible-core, and the change was unnoticed until
reported.

With this patch, we ensure all PRs checks are executed against the
minimun supported ansible-core version, so we can ensure that both
documentation and role metadata are correct and still valid.
2023-09-19 15:54:38 -03:00
Rafael Guterres Jeffman
fb75aed663 Merge pull request #1141 from t-woerner/new_idoverridegroup_module
New idoverridegroup management module.
2023-09-18 11:05:30 -03:00
Thomas Woerner
6f5bb9eebf New idoverridegroup management module.
There is a new idoverridegroup management module placed in the plugins
folder:

    plugins/modules/ipaidoverridegroup.py

The idoverridegroup module allows to ensure presence and absence of
idoverrides for groups.

Here is the documentation for the module:

    README-idoverridegroup.md

New example playbooks have been added:

    playbooks/idoverridegroup/idoverridegroup-absent.yml
    playbooks/idoverridegroup/idoverridegroup-present.yml

New tests for the module can be found at:

    tests/idoverridegroup/test_idoverridegroup.yml
    tests/idoverridegroup/test_idoverridegroup_client_context.yml
2023-09-18 15:17:08 +02:00
Rafael Guterres Jeffman
e5b2c122ce Merge pull request #1139 from t-woerner/new_idoverrideuer_module
New idoverrideuser management module.
2023-09-16 09:03:48 -03:00
Thomas Woerner
c0692e1746 New idoverrideuser management module.
There is a new idoverrideuser management module placed in the plugins
folder:

    plugins/modules/ipaidoverrideuser.py

The idoverrideuser module allows to ensure presence and absence of
idoverrides for users and certificate members.

Here is the documentation for the module:

    README-idoverrideuser.md

New example playbooks have been added:

    playbooks/idoverrideuser/idoverrideuser-absent.yml
    playbooks/idoverrideuser/idoverrideuser-certificate-absent.yml
    playbooks/idoverrideuser/idoverrideuser-certificate-present.yml
    playbooks/idoverrideuser/idoverrideuser-present.yml

New tests for the module can be found at:

    tests/idoverrideuser/test_idoverrideuser.yml
    tests/idoverrideuser/test_idoverrideuser_client_context.yml
2023-09-15 18:35:21 +02:00
Rafael Guterres Jeffman
2d079c8eec Merge pull request #1142 from t-woerner/Do_not_use_del_os.environ
Do not use "del os.environ" as the variable might not exist
2023-09-14 11:19:03 -03:00
Thomas Woerner
b70a1ecf61 Do not use "del os.environ" as the variable might not exist
The use of del os.environ assumes that the environment variable exists.
If the variable does not exist, this call will result in a traceback.
The solution is to use os.environ.pop(VARIABLE, None) instead.

This is the ansible-freeipa fix for https://pagure.io/freeipa/issue/9446
(Nightly test failure for replica installation with --setup-ca)
2023-09-14 15:20:37 +02:00
Rafael Guterres Jeffman
7cb5e481e5 Merge pull request #1140 from t-woerner/new_module_template_fixes
new_module template fixes
2023-09-13 11:07:02 -03:00
Thomas Woerner
60593b7dd3 utils/templates/ipamodule*.py.in: Fix superfluous type in argument spec
The type was given twice for state and action argument specs. This has
been fixed.
2023-09-13 13:20:47 +02:00
Thomas Woerner
e84ed3b6ba utils/templates/test_module_client_context.yml.in: Fix FQDN issue
Fixes left over FQDN issue for include_tasks.
2023-09-13 13:09:24 +02:00
Rafael Guterres Jeffman
6e1f9f1a72 pylint: Fix redefined-builtin 2023-09-11 12:01:17 -03:00
Rafael Guterres Jeffman
46a307aaeb pylint: Fix unused-argument 2023-09-11 12:01:17 -03:00
Rafael Guterres Jeffman
d8f8211a1c ci: Bump pylint version
Change pylint version to match latest version on Fedora 38.
2023-09-11 12:01:17 -03:00
Rafael Guterres Jeffman
34daa992f5 development: Bump versions of development checks
Update versions for linters and pre-commit checks, and fix ansible-lint
execution.
2023-09-11 12:01:17 -03:00
Rafael Guterres Jeffman
07c1a5ee61 pylint: Unnecessary parens after '=' keyword
This patch removes unnecessary usage of parens on attributions.
2023-09-11 12:01:17 -03:00
Rafael Guterres Jeffman
63d0272385 Change 'Exception' to 'RuntimeError' when FreeIPA version is too old
Changing the use of 'Exception' to 'RuntimeError' has the benefits of
making the error more specific and meaningful for what is being reported
and to remove warnings from linters (pylint).

The same change is applied to all deployment roles.
2023-09-11 12:01:17 -03:00
Rafael Guterres Jeffman
d0a8005a7f pylint: Disable broad exception warnings
In recent pylint versions, use of broad exceptions for both raise and
try/except blocks raise a linter warning. As its use is justifiable in
the case of ipavault, the warnings are disabled where they occur.
2023-09-11 12:01:17 -03:00
Rafael Guterres Jeffman
24efad73fa pylint: Fix warning 'unnecessary "else" after "return"'
Recent pylint versions warn against the use of an 'else' in a
'try-except' block if using a 'return' on the 'except' part is is the
idom used by ansible-freeipa when retrieving IPA data objects.

This change removes the usage of the 'else:' in such cases, and modify
the templates so that new modules do not have the same issue in the
future.
2023-09-11 12:01:17 -03:00
Rafael Guterres Jeffman
fd1352ad7e pylint: Disable warning when using non-literal dict
Recent pylint versions warns when a dictionaire is created using
'dict()' instead of '{...}'. Using 'dict()' in ansible-freeipa modules
actually enhances readability, so this change disables the check for
'use-dict-literal' in pylint.
2023-09-11 12:01:17 -03:00
Thomas Woerner
de38e8f0bc Merge pull request #1138 from rjeffman/fix_spec_update_modules
spec file: Updated list of modules
2023-09-11 10:55:31 +02:00
Rafael Guterres Jeffman
847ae2a374 spec file: Updated list of modules
Current spec file template was missing certificate, netgroup and
idview modules.
2023-09-08 11:21:33 -03:00
Thomas Woerner
bcee9aba92 Merge pull request #1136 from rjeffman/bump_ansible_version_2.13
Bump Ansible version to 2.13
2023-09-08 15:47:57 +02:00
Rafael Guterres Jeffman
c34c66fa79 ansible-freeipa: Bump minimum supported Ansible version to 2.13
As ansible-freeipa roles does not support Ansible 2.8, bump the
collection version to the currently oldest supported Ansible version
available, 2.13.
2023-09-08 09:59:11 -03:00
Rafael Guterres Jeffman
0a3cd06c6e README-*: Bump minimum supported Ansible version to 2.13
As ansible-freeipa roles do not support version 2.8 anymore, change the
minimum supported version to 2.13, which is the currently minimum
available and supported Ansible version.

This patch fixes documentation on all plugin READMEs, spec file and
module templates.
2023-09-08 09:59:11 -03:00
Rafael Guterres Jeffman
b5b22c3f7e roles: Bump minimum Ansible version to 2.13
Currently, the minimum supported Ansible version is 2.13, and
ansible-freeipa roles does not work with any version less than 2.9,
altough ansible-freeipa documentation states that the minimum version to
use is 2.8.

This patch fixes documentation and roles metadata to require that the
minimum Ansible version used is 2.13.
2023-09-08 09:59:11 -03:00
Thomas Woerner
7ee385ee02 Merge pull request #1131 from rjeffman/roles_update_supported_distros
Updated supported distros
2023-09-07 13:05:24 +02:00
Thomas Woerner
7d9e4da9df Merge pull request #1120 from rjeffman/ci_fix_sanity_test_ansible_lint
upstream ci: fix sanity test ansible lint failures
2023-09-07 13:03:53 +02:00
Thomas Woerner
0a20b5902d Merge pull request #1133 from rjeffman/testday_1
ipacert: Fix revocation example playbook on README
2023-09-07 13:02:13 +02:00
Rafael Guterres Jeffman
be9a2db404 Merge pull request #1134 from t-woerner/new_idview_module
New idview management module.
2023-09-06 09:53:23 -03:00
Thomas Woerner
ba4a360520 New idview management module.
There is a new idview management module placed in the plugins folder:

    plugins/modules/ipaidview.py

The idview module allows to ensure presence and absence of idviews and
idview host members.

Here is the documentation for the module:

    README-idview.md

New example playbooks have been added:

    playbooks/idview/idview-absent.yml
    playbooks/idview/idview-host-applied.yml
    playbooks/idview/idview-host-unapplied.yml
    playbooks/idview/idview-present.yml

New tests for the module can be found at:

    tests/idview/test_idview.yml
    tests/idview/test_idview_client_context.yml
2023-09-06 12:40:32 +02:00
Rafael Guterres Jeffman
3534fcdce7 ansible-lint: Use the same command line as galaxy-importer
Currently, there is a hard coded timeout in galaxy-importer that
prevents larger collections to execute the ansible-lint step [1].

This patch modifies the calls to ansible-lint on development tools and
upstream CI to use the same arguments as galaxy-importer and disables
the execution of the ansible-lint step for the Ansible's sanity test.

Requested ansible-lint version for tools is also updated, as a more
recent one is required.

This change will not allow development using an environment using Python
2.7, due to newer ansible-lint requirements. Roles and modules tests
against target nodes using Python 2.7 is still possible.

[1]: https://github.com/ansible/galaxy-importer/pull/231
2023-09-05 14:10:09 -03:00
Rafael Guterres Jeffman
f0f21fc8aa ipacert: Fix revocation example playbook on README
The revocation example playbook on README was wrong as it didn't have a
'reason' set, and the parameter must be used with 'state: revoked'.

This patch fixes the example and adds a new example using a reason
mnemonic instead of a reason number.
2023-08-24 08:50:18 -03:00
Rafael Guterres Jeffman
5ed96eda05 Updated supported distros
Updated all roles README files to add supported distros, as CentOS
Stream is supported (both 8 and 9) and also Debian clients.
2023-08-23 15:35:04 -03:00
Thomas Woerner
cf779e43bb Merge pull request #1123 from rjeffman/ci_increase_test_verbosity
ci: Increase verbosity for Ansible playbook runs
2023-07-24 10:05:25 +02:00
Rafael Guterres Jeffman
1a48a0fb63 Merge pull request #1122 from t-woerner/fix_ipa_command_invalid_param_choices_for_IPA_4_6
ansible_freeipa_module: Fix ipa_command_invalid_param_choices
2023-07-21 16:15:40 -03:00
Rafael Guterres Jeffman
ed3a0d5a1b ci: Increase verbosity for Ansible playbook runs
Some test failures requires more information than just the playbook
simple output. By increasing verbosity, the used parameters and the
failed line will be visible in the test error report, making it easier
to identify, reproduce and fix the issue.
2023-07-21 12:06:47 -03:00
Thomas Woerner
d58b492f1d ansible_freeipa_module: Fix ipa_command_invalid_param_choices
Fix ipa_command_invalid_param_choices for IPA 4.6 (RHEL-7)

- krbprincipalauthind in host_add does not have choices defined
- krbprincipalauthind in service_add does not have choices defined

api.Command[command].params[name].cli_metavar returns "STR" and
ast.literal_eval failes with a ValueError "malformed string".

There is no way to verify that the given values are valid or not in
this case. The check is done later on while applying the change
with host_add, host_mod, service_add and service_mod.
2023-07-21 16:44:04 +02:00
Thomas Woerner
88d4a36e17 Merge pull request #1055 from rjeffman/ipauser_idp_attrs
ipauser: Support for External IdP attributes.
2023-07-20 14:00:48 +02:00
Rafael Guterres Jeffman
6fa8223662 ipauser: Support for External IdP attributes.
Add support for 'idp' and 'idp_user_id' to ipauser plugin.

FreeIPA 4.10.0 is required for both attributes.
2023-07-19 14:38:30 -03:00
Rafael Guterres Jeffman
c9e8656494 Merge pull request #1119 from t-woerner/update_authtypes_authind_readmes
Update authtypes authind readmes
2023-07-19 11:37:34 -03:00
Thomas Woerner
a791c6a0ca README-user.md: Add choices pkinit, hardened and idp to user_auth_type
The parameter user_auth_type has been updated in FreeIPA. The choices
pkinit, hardened and idp have been missing and are now added.
2023-07-19 16:17:10 +02:00
Thomas Woerner
9cbccdade9 README-service.md: Add choice idp to auth_ind
The parameter auth_ind has been updated in FreeIPA. The choice
idp have been missing and is now added.
2023-07-19 16:07:50 +02:00
Thomas Woerner
42c07d6336 README-host.md: Add choice idp to auth_ind
The parameter auth_ind has been updated in FreeIPA. The choice
idp have been missing and is now added.
2023-07-19 16:07:27 +02:00
Thomas Woerner
a728a8d43e README-config.md: Add choices pkinit, hardened and idp to user_auth_type
The parameter user_auth_type has been updated in FreeIPA. The choices
pkinit, hardened and idp have been missing and are now added.
2023-07-19 16:07:19 +02:00
Thomas Woerner
bd3266e9f1 Merge pull request #1117 from rjeffman/doc_update_ubuntu_support
ipaserver: Update README with detailed Ubuntu support
2023-07-19 14:10:45 +02:00
Rafael Guterres Jeffman
48063d2b3a Merge pull request #1118 from t-woerner/update_authtypes_authind
Update authtypes authind
2023-07-19 08:59:18 -03:00
Thomas Woerner
5d08214516 Merge pull request #1075 from rjeffman/automount_indirect_maps
ipaautomountmap: add support for indirect maps
2023-07-19 13:53:55 +02:00
Rafael Guterres Jeffman
ef0b7e80f0 ipaserver: Update README with detailed Ubuntu support
Ubuntu does not have a FreeIPA server package since version 20.04. As
versions 16.04 (Xenial Xerus) and 18.04 (Bionic Beaver) will be
supported by Canonical until 2026 and 2028, repectively, we should keep
existing support for both versions in the ipaserver, ipareplica and
ipabackup roles until them.

This patch changes documentation to reflect that only those versions are
supported.
2023-07-19 08:51:06 -03:00
Rafael Guterres Jeffman
a33fcf45f8 ipaautomountmap: add support for indirect maps
Indirect maps were not supported by ansible-freeipa ipaautomountmap.
This patch adds support for adding indirect automount maps using the
"parent" and "mount" parameters, if the map do not yet exist. An
existing map cannot be modified.

The "parent" parameter must match an existing automount map, and the
"mount" parameter is required if "parent" is used.

A new example playbook can be found at:

    playbooks/automount/automount-map-indirect-map.yml

A new test playbook was added to test the feature:

    tests/automount/test_automountmap_indirect.yml
2023-07-19 08:41:25 -03:00
Thomas Woerner
c4b273c896 ipauser: Add choices pkinit, hardened and idp to user_auth_type
The parameter user_auth_type has been updated in FreeIPA. The choices
pkinit, hardened and idp have been missing and are now added.

An additional check was added to verify that the values of the
user_auth_type list are valid for the used IPA version.
2023-07-19 11:38:14 +02:00
Thomas Woerner
62d34d0a22 ipaservice: Add choice idp to auth_ind
The parameter auth_ind has been updated in FreeIPA. The choice
idp have been missing and is now added.

An additional check was added to verify that the values of the
auth_ind list are valid for the used IPA version.
2023-07-19 11:38:14 +02:00
Thomas Woerner
3ed0c229c4 ipahost: Add choice idp to auth_ind
The parameter auth_ind has been updated in FreeIPA. The choice
idp have been missing and is now added.

An additional check was added to verify that the values of the
auth_ind list are valid for the used IPA version.
2023-07-19 11:38:14 +02:00
Thomas Woerner
c089c010e6 ipaconfig: Add choices pkinit, hardened and idp to user_auth_type
The parameter user_auth_type has been updated in FreeIPA. The choices
pkinit, hardened and idp have been missing and are now added.

An additional check was added to verify that the values of the
user_auth_type list are valid for the used IPA version.
2023-07-19 11:38:14 +02:00
Thomas Woerner
cfbdd83a64 ansible_freeipa_module: New ipa_command_invalid_param_choices method
New IPAAnsibleModule.ipa_command_invalid_param_choices method to return
invalid parameter choices for an IPA command.

This is needed to verify for example if userauthtype and authind are
supporting the idp value.
2023-07-19 11:38:06 +02:00
Thomas Woerner
fef1bdcf8e Merge pull request #1116 from rjeffman/fix_runtests_collections
utils/run-tests.sh: Install Ansible collections on virtual environment
2023-07-17 15:35:54 +02:00
Thomas Woerner
411d363d91 Merge pull request #1056 from rjeffman/ipauser_smb_params
ipauser: Add support for SMB attributes.
2023-07-17 15:24:22 +02:00
Rafael Guterres Jeffman
1555132d85 utils/run-tests.sh: Install Ansible collections on virtual environment
When runing tests using 'utils/run-tests.sh' from inside an existing
Python virtual environment the Ansible collections are not installed due
to the order of execution of the script. On a machine that does not have
the 'containers.*' collection the test fails as there is no container
connector available.

This patch moves the section that installs Ansible collections to run
after the virtual environment is configured, and then install the
collections (usually, only 'containers.podman'), allowing the tests to
be executed.
2023-07-15 14:55:44 -03:00
Rafael Guterres Jeffman
57ad57dda3 ipauser: Add support for SMB attributes.
Since FreeIPA version 4.8.0 ipauser has support for smb-logon-script,
smb-profile-path, smb-home-dir, and smb-home-drive drive attributes.

On FreeIPA, these attributes are only available when modifying a user,
so if the user defined in the playbook does not exist, two calls to IPA
API are executed, a 'user_add' followed by a 'user_mod'.
(see https://github.com/freeipa/freeipa/blob/master/doc/designs/adtrust/samba-domain-controller.md

A new example playbook can be found at:

     playbooks/user/smb-attributes.yml

A new test playbook can be found at:

     tests/user/test_user_smb_attrs.yml
2023-07-14 10:53:30 -03:00
Thomas Woerner
dab64c7cf6 Merge pull request #1098 from rjeffman/doc_diferentiate_location_host_and_server
doc: Differentiate location meaning between host and server
2023-07-14 15:39:37 +02:00
Rafael Guterres Jeffman
b7145bc2cc doc: Differentiate location meaning between host and server
Host location and server location have very different meanings in IPA.
ipahost uses 'location' as an optional hint to where the host may be
physically located, ipaserever uses location to identify which DNS
location the server is part of.

This change updates documentation to make attribute description more
clear. Surrounding text have been changed to match text style as used in
other plugins.

This patch is related to: https://github.com/freeipa/freeipa/pull/6840
2023-07-14 10:25:51 -03:00
Thomas Woerner
c9f1da5d6b Merge pull request #1076 from rjeffman/fix_usercheck_dictcheck
Fix handling of ipapwpolicy attributes usercheck and dictcheck
2023-07-14 15:25:20 +02:00
Thomas Woerner
f4070f6a30 Merge pull request #1100 from rjeffman/ci_update_ansible_2_15
upstream CI: Update ansible-core version
2023-07-14 15:11:21 +02:00
Thomas Woerner
ad9a03ece6 Merge pull request #1114 from rjeffman/remove_virtualenv
Remove dependency on 'virtualenv'
2023-07-14 15:05:09 +02:00
Rafael Guterres Jeffman
1bfe6888a4 Remove dependency on 'virtualenv'
'virtualenv' is an external dependency with the same purpose of Python's
'venv' module. This patch removes the external dependency in favor of
the readily available package.
2023-07-13 15:07:08 -03:00
Thomas Woerner
51ddaa6491 Merge pull request #1044 from rjeffman/ipauser_street
ipauser: Add support for parameter "street"
2023-07-12 20:34:49 +02:00
Rafael Guterres Jeffman
f56861cc15 ipauser: Add support for parameter "street"
ipauser plugin was missing user parameter "street".

Tests were updated to reflect the new parameter.
2023-07-12 12:31:26 -03:00
Thomas Woerner
c4de680497 Merge pull request #1039 from rjeffman/ipauser_gecos
ipauser: Add support to modify GECOS field.
2023-07-12 17:08:53 +02:00
Rafael Guterres Jeffman
7b2701b985 ipapwpolicy: Updated module documentation.
Most of ipapwpolicy parameters can be set to an empty string ("") so
that the policy is not applied to pwpolicy. This was not refelected on
the documentation.

This change adds 'or ""' to all the fields that can be disabled by
setting it to an empty string. Also, `data types were reviewed and fixed.
2023-07-11 10:15:43 -03:00
Rafael Guterres Jeffman
694c717829 ipapwpolicy: Modify handling of usercheck and dictcheck
Modified handling of boolean values by using Ansible's 'boolean()' check
function so that a string can be used and either a bool value is
accepted or an empty string.

As the error message was changed to use the same Ansible message, tests
were also updated.
2023-07-11 10:15:43 -03:00
Rafael Guterres Jeffman
083396e133 module_utils: Export Ansible's 'boolean' parsing function.
Export Ansible's 'boolean' parsing function so it can be used to verify
if a string can be handled as a truthy value, allowing module parameters
to use strings instead of bools, as strings can be cleared by using
empty strings.
2023-07-11 08:33:35 -03:00
Rafael Guterres Jeffman
9a8a1db38f ipauser: Add support to modify GECOS field.
This patch adds a new parameter to ipauser, 'gecos', which can be used
to set the 'gecos' field of an IPA user. The default behavior of
automatically set the GECOS field to "<first> <last>" is not modified,
it is only possible to change the field to a custom value.

No validation on the value provided is done, as it is with FreeIPA.
2023-07-10 14:34:44 -03:00
Rafael Guterres Jeffman
8f9c344bc1 Merge pull request #1106 from renich/patch-1
Singular to plural on random serial numbers setting
2023-06-15 11:15:55 -03:00
Renich Bon Ciric
067b683b81 Singular to plural on random serial numbers setting
The setting was in singular in the example while being documented in plural form.
2023-06-14 16:53:22 -06:00
Rafael Guterres Jeffman
51f64e4393 upstream CI: Update ansible-core version
ansible-core 2.15 has been released on May 15th, 2023, and version 2.12
has reached EOL on May 22nd, 2023.

This patch updates the ansible-core versions used on upstream CI tests
to reflect Ansible's new releases.
2023-06-09 10:05:47 -03:00
153 changed files with 6329 additions and 478 deletions

View File

@@ -11,7 +11,5 @@ jobs:
- uses: actions/checkout@v3.1.0
with:
fetch-depth: 0
- name: Install virtualenv using pip
run: pip install virtualenv
- name: Run ansible-test
run: bash tests/sanity/sanity.sh

View File

@@ -5,23 +5,6 @@ on:
- pull_request
jobs:
check_docs_oldest_supported:
name: Check Ansible Documentation with ansible-core 2.12.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
with:
fetch-depth: 0
- uses: actions/setup-python@v4.3.0
with:
python-version: '3.x'
- name: Install Ansible 2.12
run: |
python -m pip install "ansible-core >=2.12,<2.13"
- name: Run ansible-doc-test
run: |
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
check_docs_previous:
name: Check Ansible Documentation with ansible-core 2.13.
runs-on: ubuntu-latest
steps:
@@ -38,7 +21,7 @@ jobs:
run: |
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
check_docs_current:
check_docs_previous:
name: Check Ansible Documentation with ansible-core 2.14.
runs-on: ubuntu-latest
steps:
@@ -55,6 +38,23 @@ jobs:
run: |
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
check_docs_current:
name: Check Ansible Documentation with ansible-core 2.15.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
with:
fetch-depth: 0
- uses: actions/setup-python@v4.3.0
with:
python-version: '3.x'
- name: Install Ansible 2.15
run: |
python -m pip install "ansible-core >=2.15,<2.16"
- name: Run ansible-doc-test
run: |
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
check_docs_ansible_latest:
name: Check Ansible Documentation with latest Ansible version.
runs-on: ubuntu-latest

View File

@@ -16,10 +16,10 @@ jobs:
python-version: "3.x"
- name: Run ansible-lint
run: |
pip install "ansible-core >=2.14,<2.15" ansible-lint
pip install "ansible-core>=2.16,<2.17" 'ansible-lint>=6.21'
utils/build-galaxy-release.sh -ki
cd .galaxy-build
ansible-lint
ansible-lint --profile production --exclude tests/integration/ --exclude tests/unit/ --parseable --nocolor
yamllint:
name: Verify yamllint
@@ -76,7 +76,7 @@ jobs:
python-version: "3.x"
- name: Run pylint
run: |
pip install pylint==2.14.4 wrapt==1.14.0
pip install 'pylint>=3.0'
pylint plugins roles --disable=import-error
shellcheck:

View File

@@ -1,22 +1,32 @@
---
repos:
- repo: https://github.com/ansible/ansible-lint.git
rev: v6.6.1
rev: v6.22.0
hooks:
- id: ansible-lint
always_run: false
pass_filenames: true
files: \.(yaml|yml)$
exclude: /env[^/]*.(yaml|yml)$
entry: |
env ANSIBLE_LIBRARY=./plugins/modules ANSIBLE_MODULE_UTILS=./plugins/module_utils ANSIBLE_DOC_FRAGMENT_PLUGINS=./plugins/doc_fragments ansible-lint
entry: |-
env
ANSIBLE_LIBRARY=./plugins/modules
ANSIBLE_MODULE_UTILS=./plugins/module_utils
ANSIBLE_DOC_FRAGMENT_PLUGINS=./plugins/doc_fragments
ansible-lint
--offline
--profile production
--exclude tests/integration/
--exclude tests/unit/
--parseable
--nocolor
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.28.0
rev: v1.32.0
hooks:
- id: yamllint
files: \.(yaml|yml)$
- repo: https://github.com/pycqa/flake8
rev: 5.0.3
rev: 6.0.0
hooks:
- id: flake8
- repo: https://github.com/pycqa/pydocstyle
@@ -24,7 +34,7 @@ repos:
hooks:
- id: pydocstyle
- repo: https://github.com/pycqa/pylint
rev: v2.14.4
rev: v3.0.2
hooks:
- id: pylint
args:

View File

@@ -22,7 +22,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -21,7 +21,7 @@ FreeIPA versions 4.4.0 and up are supported by the ipaautomountkey module.
Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -21,7 +21,7 @@ FreeIPA versions 4.4.0 and up are supported by the ipaautomountlocation module.
Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -21,7 +21,7 @@ FreeIPA versions 4.4.0 and up are supported by the ipaautomountmap module.
Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)
@@ -54,6 +54,21 @@ Example playbook to ensure presence of an automount map:
desc: "this is a map for servers in the DMZ"
```
Automount maps can contain a submount key, which defines a mount location within the map the references another map. On FreeIPA, this is known as an indirect map. An indirect automount map is equivalent to adding a proper automount key to a map, referencyng another map (this second map is the indirect map). Use `parent` and `mount` parameters to create an indirect automount map with ansible-freeipa, without the need to directly manage the automount keys.
Example playbook to ensure an indirect automount map is present:
```yaml
---
- name: Playbook to add an indirect automount map
ipaautomountmap:
ipaadmin_password: SomeADMINpassword
name: auto.indirect
location: DMZ
parent: auto.DMZ
mount: dmz_indirect
```
Example playbook to ensure auto.DMZi is absent:
```yaml
@@ -81,16 +96,14 @@ Variable | Description | Required
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
`name` \| `mapname` \| `map` \| `automountmapname` | Name of the map to manage | yes
`location` \| `automountlocation` \| `automountlocationcn` | Location name. | yes
`parentmap` | Parent map of the indirect map. Can only be used when creating new maps. Default: auto.master | no
`mount` | Indirect map mount point, relative to parent map. | yes, if `parent` is used.
`desc` \| `description` | Description of the map | yes
`state` | The state to ensure. It can be one of `present`, or `absent`, default: `present`. | no
Notes
=====
Creation of indirect mount points are not supported.
Authors
=======
Chris Procter
- Chris Procter
- Rafael Jeffman

View File

@@ -25,7 +25,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
* Some tool to generate a certificate signing request (CSR) might be needed, like `openssl`.
**Node**
@@ -77,6 +77,23 @@ Example playbook to revoke an existing certificate:
ipacert:
ipaadmin_password: SomeADMINpassword
serial_number: 123456789
reason: 5
state: revoked
```
When revoking a certificate a mnemonic can also be used to set the revocation reason:
```yaml
---
- name: Revoke certificate
hosts: ipaserver
tasks:
- name Revoke a certificate
ipacert:
ipaadmin_password: SomeADMINpassword
serial_number: 123456789
reason: cessationOfOperation
state: revoked
```

View File

@@ -25,7 +25,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)
@@ -145,7 +145,7 @@ Variable | Description | Required
`selinuxusermaporder` \| `ipaselinuxusermaporder`| Set ordered list in increasing priority of SELinux users | no
`selinuxusermapdefault`\| `ipaselinuxusermapdefault` | Set default SELinux user when no match is found in SELinux map rule | no
`pac_type` \| `ipakrbauthzdata` | set default types of PAC supported for services (choices: `MS-PAC`, `PAD`, `nfs:NONE`). Use `""` to clear this variable. | no
`user_auth_type` \| `ipauserauthtype` | set default types of supported user authentication (choices: `password`, `radius`, `otp`, `disabled`). Use `""` to clear this variable. | no
`user_auth_type` \| `ipauserauthtype` | set default types of supported user authentication (choices: `password`, `radius`, `otp`, `pkinit`, `hardened`, `idp`, `disabled`, `""`). An additional check ensures that only types can be used that are supported by the IPA version. Use `""` to clear this variable. | no
`domain_resolution_order` \| `ipadomainresolutionorder` | Set list of domains used for short name qualification | no
`ca_renewal_master_server` \| `ipacarenewalmasterserver`| Renewal master for IPA certificate authority. | no
`enable_sid` | New users and groups automatically get a SID assigned. Cannot be deactivated once activated. Requires IPA 4.9.8+. (bool) | no

View File

@@ -23,7 +23,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -22,7 +22,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -21,7 +21,7 @@ FreeIPA versions 4.4.0 and up are supported by the ipadnsforwardzone module.
Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -22,7 +22,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -23,7 +23,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**

View File

@@ -29,7 +29,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -22,7 +22,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -22,7 +22,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -22,7 +22,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -24,7 +24,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)
@@ -335,7 +335,7 @@ Variable | Description | Required
-------- | ----------- | --------
`description` | The host description. | no
`locality` | Host locality (e.g. "Baltimore, MD"). | no
`location` \| `ns_host_location` | Host location (e.g. "Lab 2"). | no
`location` \| `ns_host_location` | Host physical location hint (e.g. "Lab 2"). | no
`platform` \| `ns_hardware_platform` | Host hardware platform (e.g. "Lenovo T61"). | no
`os` \| `ns_os_version` | Host operating system and version (e.g. "Fedora 9"). | no
`password` \| `user_password` \| `userpassword` | Password used in bulk enrollment for absent or not enrolled hosts. | no
@@ -354,7 +354,7 @@ Variable | Description | Required
`mac_address` \| `macaddress` | List of hardware MAC addresses. | no
`sshpubkey` \| `ipasshpubkey` | List of SSH public keys | no
`userclass` \| `class` | Host category (semantics placed on this attribute are for local interpretation) | no
`auth_ind` \| `krbprincipalauthind` | Defines an allow list for Authentication Indicators. Use 'otp' to allow OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA authentications. Use empty string to reset auth_ind to the initial value. Other values may be used for custom configurations. choices: ["radius", "otp", "pkinit", "hardened", ""] | no
`auth_ind` \| `krbprincipalauthind` | Defines an allow list for Authentication Indicators. Use 'otp' to allow OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA authentications. Use empty string to reset auth_ind to the initial value. Other values may be used for custom configurations. An additional check ensures that only types can be used that are supported by the IPA version. Choices: ["radius", "otp", "pkinit", "hardened", "idp", ""] | no
`requires_pre_auth` \| `ipakrbrequirespreauth` | Pre-authentication is required for the service (bool) | no
`ok_as_delegate` \| `ipakrbokasdelegate` | Client credentials may be delegated to the service (bool) | no
`ok_to_auth_as_delegate` \| `ipakrboktoauthasdelegate` | The service is allowed to authenticate on behalf of a client (bool) | no

View File

@@ -26,7 +26,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

233
README-idoverridegroup.md Normal file
View File

@@ -0,0 +1,233 @@
Idoverridegroup module
============
Description
-----------
The idoverridegroup module allows to ensure presence and absence of idoverridegroups and idoverridegroup members.
Use Cases
---------
With idoverridegroup it is possible to manage group attributes within ID views. These attributes are for example the group name or gid.
Features
--------
* Idoverridegroup management
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipaidoverridegroup module.
Requirements
------------
**Controller**
* Ansible version: 2.13
**Node**
* Supported FreeIPA version (see above)
Usage
=====
Example inventory file
```ini
[ipaserver]
ipaserver.test.local
```
Example playbook to make sure test group test_group is present in idview test_idview
```yaml
---
- name: Playbook to manage idoverridegroup
hosts: ipaserver
become: false
tasks:
- name: Ensure test group test_group is present in idview test_idview.
ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
```
Example playbook to make sure test group test_group is present in idview test_idview with description
```yaml
---
- name: Playbook to manage idoverridegroup
hosts: ipaserver
become: false
tasks:
- name: Ensure test group test_group is present in idview test_idview with description
ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
description: "test_group description"
```
Example playbook to make sure test group test_group is present in idview test_idview without description
```yaml
---
- name: Playbook to manage idoverridegroup
hosts: ipaserver
become: false
tasks:
- name: Ensure test group test_group is present in idview test_idview without description
ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
description: ""
```
Example playbook to make sure test group test_group is present in idview test_idview with internal name test_123_group
```yaml
---
- name: Playbook to manage idoverridegroup
hosts: ipaserver
become: false
tasks:
- name: Ensure test group test_group is present in idview test_idview with internal name test_123_group
ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
name: test_123_group
```
Example playbook to make sure test group test_group is present in idview test_idview without internal name
```yaml
---
- name: Playbook to manage idoverridegroup
- name: Ensure test group test_group is present in idview test_idview without internal name
hosts: ipaserver
become: false
tasks:
- ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
name: ""
```
Example playbook to make sure test group test_group is present in idview test_idview with gid 20001
```yaml
---
- name: Playbook to manage idoverridegroup
hosts: ipaserver
become: false
tasks:
- name: Ensure test group test_group is present in idview test_idview with gid 20001
ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
gid: 20001
```
Example playbook to make sure test group test_group is present in idview test_idview without gid
```yaml
---
- name: Playbook to manage idoverridegroup
hosts: ipaserver
become: false
tasks:
- name: Ensure test group test_group is present in idview test_idview without gid
ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
gid: ""
```
Example playbook to make sure test group test_group is present in idview test_idview with enabling falling back to AD DC LDAP when resolving AD trusted objects. (For two-way trusts only.)
```yaml
---
- name: Playbook to manage idoverridegroup
hosts: ipaserver
become: false
tasks:
- name: Ensure test group test_group is present in idview test_idview with fallback_to_ldap enabled
ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
fallback_to_ldap: true
```
Example playbook to make sure test group test_group is absent in idview test_idview
```yaml
---
- name: Playbook to manage idoverridegroup
hosts: ipaserver
become: false
tasks:
- name: Ensure test group test_group is absent in idview test_idview
ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
continue: true
state: absent
```
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
`idview` \| `idviewcn` | The doverridegroup idview string. | yes
`anchor` \| `ipaanchoruuid` | The list of anchors to override. | yes
`description` \| `desc` | Description | no
`name` \| `group_name` \| `cn` | The group. | no
`gid` \| `gidnumber` | Group ID Number (int or "") | no
`fallback_to_ldap` | Allow falling back to AD DC LDAP when resolving AD trusted objects. For two-way trusts only. | no
`delete_continue` \| `continue` | Continuous mode. Don't stop on errors. Valid only if `state` is `absent`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no
Authors
=======
Thomas Woerner

503
README-idoverrideuser.md Normal file
View File

@@ -0,0 +1,503 @@
Idoverrideuser module
============
Description
-----------
The idoverrideuser module allows to ensure presence and absence of idoverrideusers and idoverrideuser members.
Use Cases
---------
With idoverrideuser it is possible to manage user attributes within ID views. These attributes are for example the login name, home directory, certificate for authentication or SSH keys.
Features
--------
* Idoverrideuser management
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipaidoverrideuser module.
Requirements
------------
**Controller**
* Ansible version: 2.13
**Node**
* Supported FreeIPA version (see above)
Usage
=====
Example inventory file
```ini
[ipaserver]
ipaserver.test.local
```
Example playbook to make sure test user test_user is present in idview test_idview
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview.
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
```
Example playbook to make sure test user test_user is present in idview test_idview with description
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview with description
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
description: "test_user description"
```
Example playbook to make sure test user test_user is present in idview test_idview without description
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview without description
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
description: ""
```
Example playbook to make sure test user test_user is present in idview test_idview with internal name test_123_user
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview with internal name test_123_user
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
name: test_123_user
```
Example playbook to make sure test user test_user is present in idview test_idview without internal name
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview without internal name
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
name: ""
```
Example playbook to make sure test user test_user is present in idview test_idview with uid 20001
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview with uid 20001
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
uid: 20001
```
Example playbook to make sure test user test_user is present in idview test_idview without uid
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview without uid
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
uid: ""
```
Example playbook to make sure test user test_user is present in idview test_idview with gecos "Gecos Test"
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview with gecos "Gecos Test"
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
gecos: Gecos Test
```
Example playbook to make sure test user test_user is present in idview test_idview without gecos
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview without gecos
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
gecos: ""
```
Example playbook to make sure test user test_user is present in idview test_idview with gidnumber
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview with gidnumber
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
gidnumber: 20001
```
Example playbook to make sure test user test_user is present in idview test_idview without gidnumber
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview without gidnumber
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
gidnumber: ""
```
Example playbook to make sure test user test_user is present in idview test_idview with homedir /Users
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview with homedir /Users
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
homedir: /Users
```
Example playbook to make sure test user test_user is present in idview test_idview without homedir
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview without homedir
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
homedir: ""
```
Example playbook to make sure test user test_user is present in idview test_idview with shell
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview with shell
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
shell: /bin/someshell
```
Example playbook to make sure test user test_user is present in idview test_idview without shell
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview without shell
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
shell: ""
```
Example playbook to make sure test user test_user is present in idview test_idview with sshpubkey
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview with sshpubkey
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
sshpubkey:
- ssh-rsa AAAAB3NzaC1yc2EAAADAQABAAABgQCqmVDpEX5gnSjKuv97Ay ...
```
Example playbook to make sure test user test_user is present in idview test_idview without sshpubkey
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview without sshpubkey
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
sshpubkey: []
```
Example playbook to make sure test user test_user is present in idview test_idview with 1 certificate
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview with 1 certificate
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
certificate:
- "{{ lookup('file', 'cert1.b64', rstrip=False) }}"
```
Example playbook to make sure test user test_user is present in idview test_idview with 3 certificate members
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview with 3 certificate members
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
certificate:
- "{{ lookup('file', 'cert1.b64', rstrip=False) }}"
- "{{ lookup('file', 'cert2.b64', rstrip=False) }}"
- "{{ lookup('file', 'cert3.b64', rstrip=False) }}"
action: member
```
Example playbook to make sure test user test_user is present in idview test_idview without 2 certificate members
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview without 2 certificate members
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
certificate:
- "{{ lookup('file', 'cert2.b64', rstrip=False) }}"
- "{{ lookup('file', 'cert3.b64', rstrip=False) }}"
action: member
state: absent
```
Example playbook to make sure test user test_user is present in idview test_idview without certificates
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview without certificates
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
certificate: []
```
Example playbook to make sure test user test_user is present in idview test_idview with enabling falling back to AD DC LDAP when resolving AD trusted objects. (For two-way trusts only.)
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview with fallback_to_ldap enabled
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
fallback_to_ldap: true
```
Example playbook to make sure test user test_user is absent in idview test_idview
```yaml
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is absent in idview test_idview
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
continue: true
state: absent
```
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
`idview` \| `idviewcn` | The doverrideuser idview string. | yes
`anchor` \| `ipaanchoruuid` | The list of anchors to override. | yes
`description` \| `desc` | Description | no
`name` \| `login` | The user (internally uid) | no
`uid` \| `uidnumber` | User ID Number (int or "") | no
`gecos` | GECOS | no
`gidnumber` | Group ID Number (int or ""). | no
`homedir` \| `homedirectory` | Home directory. | no
`shell` \| `loginshell` | Login shell. | no
`sshpubkey` \| `ipasshpubkey` | List of SSH public keys. | no
`certificate` \| `usercertificate` | List of Base-64 encoded user certificates. This variable can also be used with `action: member`. | no
`fallback_to_ldap` | Allow falling back to AD DC LDAP when resolving AD trusted objects. For two-way trusts only. | no
`delete_continue` \| `continue` | Continuous mode. Don't stop on errors. Valid only if `state` is `absent`. | no
`nomembers` \| `no_members` | Suppress processing of membership attributes. Valid only if `state` is `absent`. | no
`action` | Work on idoverrideuser or member level. It can be on of `member` or `idoverrideuser` and defaults to `idoverrideuser`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no
Authors
=======
Thomas Woerner

192
README-idp.md Normal file
View File

@@ -0,0 +1,192 @@
Idp module
============
Description
-----------
The idp module allows to ensure presence and absence of idps.
Features
--------
* Idp management
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipaidp module.
Requirements
------------
**Controller**
* Ansible version: 2.13
**Node**
* Supported FreeIPA version (see above)
Usage
=====
Example inventory file
```ini
[ipaserver]
ipaserver.test.local
```
Example playbook to make sure keycloak idp my-keycloak-idp is present:
```yaml
---
- name: Playbook to manage IPA idp.
hosts: ipaserver
become: false
tasks:
- name: Ensure keycloak idp my-keycloak-idp is present
ipaidp:
ipaadmin_password: SomeADMINpassword
name: my-keycloak-idp
provider: keycloak
organization: main
base_url: keycloak.idm.example.com:8443/auth
client_id: my-client-id
```
Example playbook to make sure keycloak idp my-keycloak-idp is absent:
```yaml
---
- name: Playbook to manage IPA idp.
hosts: ipaserver
become: false
tasks:
- name: Ensure keycloak idp my-keycloak-idp is absent
ipaidp:
ipaadmin_password: SomeADMINpassword
name: my-keycloak-idp
delete_continue: true
state: absent
```
Example playbook to make sure github idp my-github-idp is present:
```yaml
---
- name: Playbook to manage IPA idp.
hosts: ipaserver
become: false
tasks:
- name: Ensure github idp my-github-idp is present
ipaidp:
ipaadmin_password: SomeADMINpassword
name: my-github-idp
provider: github
client_id: my-github-client-id
```
Example playbook to make sure google idp my-google-idp is present using provider defaults without specifying provider:
```yaml
---
- name: Playbook to manage IPA idp.
hosts: ipaserver
become: false
tasks:
- name: Ensure google idp my-google-idp is present using provider defaults without specifying provider
ipaidp:
ipaadmin_password: SomeADMINpassword
name: my-google-idp
auth_uri: https://accounts.google.com/o/oauth2/auth
dev_auth_uri: https://oauth2.googleapis.com/device/code
token_uri: https://oauth2.googleapis.com/token
keys_uri: https://www.googleapis.com/oauth2/v3/certs
userinfo_uri: https://openidconnect.googleapis.com/v1/userinfo
client_id: my-google-client-id
scope: "openid email"
idp_user_id: email
```
Example playbook to make sure google idp my-google-idp is present using provider:
```yaml
---
- name: Playbook to manage IPA idp.
hosts: ipaserver
become: false
tasks:
- name: Ensure google idp my-google-idp is present using provider
ipaidp:
ipaadmin_password: SomeADMINpassword
name: my-google-idp
provider: google
client_id: my-google-client-id
```
Example playbook to make sure idps my-keycloak-idp, my-github-idp and my-google-idp are absent:
```yaml
---
- name: Playbook to manage IPA idp.
hosts: ipaserver
become: false
tasks:
- name: Ensure idps my-keycloak-idp, my-github-idp and my-google-idp are absent
ipaidp:
ipaadmin_password: SomeADMINpassword
name:
- my-keycloak-idp
- my-github-idp
- my-google-idp
delete_continue: true
state: absent
```
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) | false
`name` \| `cn` | The list of idp name strings. | yes
auth_uri \| ipaidpauthendpoint | OAuth 2.0 authorization endpoint string. | no
dev_auth_uri \| ipaidpdevauthendpoint | Device authorization endpoint string. | no
token_uri \| ipaidptokenendpoint | Token endpoint string. | no
userinfo_uri \| ipaidpuserinfoendpoint | User information endpoint string. | no
keys_uri \| ipaidpkeysendpoint | JWKS endpoint string. | no
issuer_url \| ipaidpissuerurl | The Identity Provider OIDC URL string. | no
client_id \| ipaidpclientid | OAuth 2.0 client identifier string. | no
secret \| ipaidpclientsecret | OAuth 2.0 client secret string. | no
scope \| ipaidpscope | OAuth 2.0 scope string. Multiple scopes separated by space. | no
idp_user_id \| ipaidpsub | Attribute string for user identity in OAuth 2.0 userinfo. | no
provider \| ipaidpprovider | Pre-defined template string. This provides the provider defaults, which can be overridden with the other IdP options. Choices: ["google","github","microsoft","okta","keycloak"] | no
organization \| ipaidporg | Organization ID string or Realm name for IdP provider templates. | no
base_url \| ipaidpbaseurl | Base URL string for IdP provider templates. | no
rename \| new_name | New name for the Identity Provider server object. Only with `state: renamed`. | no
delete_continue \| continue | Continuous mode. Don't stop on errors. Valid only if `state` is `absent`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, `renamed`, default: `present`. | no
Authors
=======
Thomas Woerner

View File

@@ -37,7 +37,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

153
README-idview.md Normal file
View File

@@ -0,0 +1,153 @@
Idview module
============
Description
-----------
The idview module allows to ensure presence and absence of idviews and idview host members.
Use Cases
---------
With ID views it is possible to override user or group attributes for users stored in the LDAP server. For example the login name, home directory, certificate for authentication or SSH keys. An ID view is client-side and specifies new values for user or group attributes and also the client host or hosts on which the values apply.
The ID view and the applied hosts are managed with idview, the user attributes are managed with idoverrideuser and the group attributes with idoverridegroup.
Features
--------
* Idview management
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipaidview module.
Requirements
------------
**Controller**
* Ansible version: 2.13
**Node**
* Supported FreeIPA version (see above)
Usage
=====
Example inventory file
```ini
[ipaserver]
ipaserver.test.local
```
Example playbook to make sure idview "test_idview" is present:
```yaml
---
- name: Playbook to manage IPA idview.
hosts: ipaserver
become: false
tasks:
- ipaidview:
ipaadmin_password: SomeADMINpassword
name: test_idview
```
Example playbook to make sure idview "test_idview" member host "testhost.example.com" is present:
```yaml
---
- name: Playbook to manage IPA idview host member.
hosts: ipaserver
become: false
tasks:
- ipaidview:
ipaadmin_password: SomeADMINpassword
name: test_idview
host: testhost.example.com
action: member
```
Example playbook to make sure idview "test_idview" member host "testhost.example.com" is absent:
```yaml
---
- name: Playbook to manage IPA idview host member.
hosts: ipaserver
become: false
tasks:
- ipaidview:
ipaadmin_password: SomeADMINpassword
name: test_idview
host: testhost.example.com
action: member
state: absent
```
Example playbook to make sure idview "test_idview" is present with domain_resolution_order for "ad.example.com:ipa.example.com":
```yaml
---
- name: Playbook to manage IPA idview host member.
hosts: ipaserver
become: false
tasks:
- ipaidview:
ipaadmin_password: SomeADMINpassword
name: test_idview
domain_resolution_order: "ad.example.com:ipa.example.com"
```
Example playbook to make sure idview "test_idview" is absent:
```yaml
---
- name: Playbook to manage IPA idview.
hosts: ipaserver
become: false
tasks:
- ipaidview:
ipaadmin_password: SomeADMINpassword
name: test_idview
state: absent
```
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
`name` \| `cn` | The list of idview name strings. | yes
`description` \| `desc` | The description string of the idview. | no
`domain_resolution_order` \| `ipadomainresolutionorder` | Colon-separated list of domains used for short name qualification. | no
`host` \| `hosts` | List of hosts to apply the ID View to. A host can only be applied to a single idview at any time. Applying a host that is already applied to a different idview will change the idview the host is applied to to the new one. | no
`rename` \| `new_name` | Rename the ID view object to the new name string. Only usable with `state: renamed`. | no
`delete_continue` \| `continue` | Continuous mode. Don't stop on errors. Valid only if `state` is `absent`. | no
`action` | Work on idview or member level. It can be on of `member` or `idview` and defaults to `idview`. | no
`state` | The state to ensure. It can be one of `present`, `absent` and `renamed`, default: `present`. | no
Authors
=======
Thomas Woerner

View File

@@ -22,7 +22,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -22,7 +22,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -22,7 +22,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -22,7 +22,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -22,7 +22,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)
@@ -128,20 +128,20 @@ Variable | Description | Required
`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 yes. (bool) | no
`name` \| `cn` | The list of pwpolicy name strings. If name is not given, `global_policy` will be used automatically. | no
`maxlife` \| `krbmaxpwdlife` | Maximum password lifetime in days. (int) | no
`minlife` \| `krbminpwdlife` | Minimum password lifetime in hours. (int) | no
`history` \| `krbpwdhistorylength` | Password history size. (int) | no
`minclasses` \| `krbpwdmindiffchars` | Minimum number of character classes. (int) | no
`minlength` \| `krbpwdminlength` | Minimum length of password. (int) | no
`priority` \| `cospriority` | Priority of the policy, higher number means lower priority. (int) | no
`maxfail` \| `krbpwdmaxfailure` | Consecutive failures before lockout. (int) | no
`failinterval` \| `krbpwdfailurecountinterval` | Period after which failure count will be reset in seconds. (int) | no
`lockouttime` \| `krbpwdlockoutduration` | Period for which lockout is enforced in seconds. (int) | no
`maxrepeat` \| `ipapwdmaxrepeat` | Maximum number of same consecutive characters. Requires IPA 4.9+ (int) | no
`maxsequence` \| `ipapwdmaxsequence` | The maximum length of monotonic character sequences (abcd). Requires IPA 4.9+ (int) | no
`dictcheck` \| `ipapwdictcheck` | Check if the password is a dictionary word. Requires IPA 4.9+ (int) | no
`usercheck` \| `ipapwdusercheck` | Check if the password contains the username. Requires IPA 4.9+ (int) | no
`gracelimit` \| `passwordgracelimit` | Number of LDAP authentications allowed after expiration. Requires IPA 4.9.10 (int) | no
`maxlife` \| `krbmaxpwdlife` | Maximum password lifetime in days. (int or "") | no
`minlife` \| `krbminpwdlife` | Minimum password lifetime in hours. (int or "") | no
`history` \| `krbpwdhistorylength` | Password history size. (int or "") | no
`minclasses` \| `krbpwdmindiffchars` | Minimum number of character classes. (int or "") | no
`minlength` \| `krbpwdminlength` | Minimum length of password. (int or "") | no
`priority` \| `cospriority` | Priority of the policy, higher number means lower priority. (int or "") | no
`maxfail` \| `krbpwdmaxfailure` | Consecutive failures before lockout. (int or "") | no
`failinterval` \| `krbpwdfailurecountinterval` | Period after which failure count will be reset in seconds. (int or "") | no
`lockouttime` \| `krbpwdlockoutduration` | Period for which lockout is enforced in seconds. (int or "") | no
`maxrepeat` \| `ipapwdmaxrepeat` | Maximum number of same consecutive characters. Requires IPA 4.9+ (int or "") | no
`maxsequence` \| `ipapwdmaxsequence` | The maximum length of monotonic character sequences (abcd). Requires IPA 4.9+ (int or "") | no
`dictcheck` \| `ipapwdictcheck` | Check if the password is a dictionary word. Requires IPA 4.9+. (bool or "") | no
`usercheck` \| `ipapwdusercheck` | Check if the password contains the username. Requires IPA 4.9+. (bool or "") | no
`gracelimit` \| `passwordgracelimit` | Number of LDAP authentications allowed after expiration. Requires IPA 4.9.10 (int or "") | no
`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | yes

View File

@@ -25,7 +25,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -23,7 +23,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -22,7 +22,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)
@@ -249,14 +249,14 @@ Variable | Description | Required
`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 yes. (bool) | no
`name` \| `cn` | The list of server name strings. | yes
`location` \| `ipalocation_location` | The server location string. Only in state: present. "" for location reset. | no
`service_weight` \| `ipaserviceweight` | Weight for server services. Type Values 0 to 65535, -1 for weight reset. Only in state: present. (int) | no
`hidden` | Set hidden state of a server. Only in state: present. (bool) | no
`no_members` | Suppress processing of membership attributes. Only in state: present. (bool) | no
`delete_continue` \| `continue` | Continuous mode: Don't stop on errors. Only in state: absent. (bool) | no
`ignore_last_of_role` | Skip a check whether the last CA master or DNS server is removed. Only in state: absent. (bool) | no
`ignore_topology_disconnect` | Ignore topology connectivity problems after removal. Only in state: absent. (bool) | no
`force` | Force server removal even if it does not exist. Will always result in changed. Only in state: absent. (bool) | no
`location` \| `ipalocation_location` | The server DNS location. Only available with 'state: present'. Use "" for location reset. | no
`service_weight` \| `ipaserviceweight` | Weight for server services. Type Values 0 to 65535, -1 for weight reset. Only available with 'state: present'. (int) | no
`hidden` | Set hidden state of a server. Only available with 'state: present'. (bool) | no
`no_members` | Suppress processing of membership attributes. Only avialable with 'state: present'. (bool) | no
`delete_continue` \| `continue` | Continuous mode: Don't stop on errors. Only available with 'state: absent'. (bool) | no
`ignore_last_of_role` | Skip a check whether the last CA master or DNS server is removed. Only available with 'state: absent'. (bool) | no
`ignore_topology_disconnect` | Ignore topology connectivity problems after removal. Only available with 'state: absent'. (bool) | no
`force` | Force server removal even if it does not exist. Will always result in changed. Only available with 'state: absent'. (bool) | no
`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. `present` is only working with existing servers. | no

View File

@@ -25,7 +25,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FReeIPA version (see above)
@@ -294,7 +294,7 @@ Variable | Description | Required
`name` \| `service` | The list of service name strings. | yes
`certificate` \| `usercertificate` | Base-64 encoded service certificate. | no
`pac_type` \| `ipakrbauthzdata` | Supported PAC type. It can be one of `MS-PAC`, `PAD`, or `NONE`. Use empty string to reset pac_type to the initial value. | no
`auth_ind` \| `krbprincipalauthind` | Defines an allow list for Authentication Indicators. It can be any of `otp`, `radius`, `pkinit` or `hardened`. Use empty string to reset auth_ind to the initial value. | no
`auth_ind` \| `krbprincipalauthind` | Defines an allow list for Authentication Indicators. It can be any of `otp`, `radius`, `pkinit`, `hardened`, `idp` or `""`. An additional check ensures that only types can be used that are supported by the IPA version. Use empty string to reset auth_ind to the initial value. | no
`requires_pre_auth` \| `ipakrbrequirespreauth` | Pre-authentication is required for the service. Default to true. (bool) | no
`ok_as_delegate` \| `ipakrbokasdelegate` | Client credentials may be delegated to the service. Default to false. (bool) | no
`ok_to_auth_as_delegate` \| `ipakrboktoauthasdelegate` | The service is allowed to authenticate on behalf of a client. Default to false. (bool) | no

View File

@@ -24,7 +24,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -24,7 +24,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -24,7 +24,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -24,7 +24,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -22,7 +22,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -22,7 +22,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -21,7 +21,7 @@ Requirements
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**

View File

@@ -24,7 +24,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)
@@ -58,6 +58,7 @@ Example playbook to ensure a user is present:
last: Acme
uid: 10001
gid: 100
gecos: "The Pinky"
phone: "+555123457"
email: pinky@acme.com
passwordexpiration: "2023-01-19 23:59:59"
@@ -352,6 +353,33 @@ Example playbook to ensure users are absent:
state: absent
```
When using FreeIPA 4.8.0+, SMB logon script, profile, home directory and home drive can be set for users.
In the example playbook to set SMB attributes note that `smb_profile_path` and `smb_home_dir` use paths in UNC format, which includes backslashes ('\\`). If the paths are quoted, the backslash needs to be escaped becoming "\\", so the path `\\server\dir` becomes `"\\\\server\\dir"`. If the paths are unquoted the slashes do not have to be escaped.
The YAML specification states that a colon (':') is a key separator and a dash ('-') is an item marker, only with a space after them, so using both unquoted as part of a path should not be a problem. If a space is needed after a colon or a dash, then a quoted string must be used as in `"user - home"`. For the `smb_home_drive` attribute is is recomended that a quoted string is used, to improve readability.
Example playbook to set SMB attributes:
```yaml
---
- name: Plabook to handle users
hosts: ipaserver
become: false
tasks:
- name: Ensure user 'smbuser' is present with smb attributes
ipauser:
ipaadmin_password: SomeADMINpassword
name: smbuser
first: SMB
last: User
smb_logon_script: N:\logonscripts\startup
smb_profile_path: \\server\profiles\some_profile
smb_home_dir: \\users\home\smbuser
smb_home_drive: "U:"
```
Variables
=========
@@ -395,6 +423,8 @@ Variable | Description | Required
`random` | Generate a random user password | no
`uid` \| `uidnumber` | User ID Number (system will assign one if not provided). | no
`gid` \| `gidnumber` | Group ID Number. | no
`gecos` | GECOS | no
`street` | Street address | no
`city` | City | no
`userstate` \| `st` | State/Province | no
`postalcode` \| `zip` | Postalcode/ZIP | no
@@ -407,7 +437,7 @@ Variable | Description | Required
`manager` | List of manager user names. | no
`carlicense` | List of car licenses. | no
`sshpubkey` \| `ipasshpubkey` | List of SSH public keys. | no
`userauthtype` | List of supported user authentication types. Choices: `password`, `radius`, `otp` and ``. Use empty string to reset userauthtype to the initial value. | no
`userauthtype` \| `ipauserauthtype` | List of supported user authentication types. Choices: `password`, `radius`, `otp`, `pkinit`, `hardened`, `idp` and `""`. An additional check ensures that only types can be used that are supported by the IPA version. Use empty string to reset userauthtype to the initial value. | no
`userclass` | User category. (semantics placed on this attribute are for local interpretation). | no
`radius` | RADIUS proxy configuration | no
`radiususer` | RADIUS proxy username | no
@@ -415,6 +445,8 @@ Variable | Description | Required
`employeenumber` | Employee Number | no
`employeetype` | Employee Type | no
`preferredlanguage` | Preferred Language | no
`idp` \| `ipaidpconfiglink` | External IdP configuration | no
`idp_user_id` \| `ipaidpsub` | A string that identifies the user at external IdP | no
`certificate` | List of base-64 encoded user certificates. | no
`certmapdata` | List of certificate mappings. Either `data` or `certificate` or `issuer` together with `subject` need to be specified. Only usable with IPA versions 4.5 and up. <br>Options: | no
&nbsp; | `certificate` - Base-64 encoded user certificate, not usable with other certmapdata options. | no
@@ -422,6 +454,10 @@ Variable | Description | Required
&nbsp; | `subject` - Subject of the certificate, only usable together with `issuer` option. | no
&nbsp; | `data` - Certmap data, not usable with other certmapdata options. | no
`noprivate` | Do not create user private group. (bool) | no
`smb_logon_script` \| `ipantlogonscript` | SMB logon script path. Requires FreeIPA version 4.8.0+. | no
`smb_profile_path:` \| `ipantprofilepath` | SMB profile path, in UNC format. Requires FreeIPA version 4.8.0+. | no
`smb_home_dir` \| `ipanthomedirectory` | SMB Home Directory, in UNC format. Requires FreeIPA version 4.8.0+. | no
`smb_home_drive` \| `ipanthomedirectorydrive` | SMB Home Directory Drive, a single upercase letter (A-Z) followed by a colon (:), for example "U:". Requires FreeIPA version 4.8.0+. | no
`nomembers` | Suppress processing of membership attributes. (bool) | no
@@ -442,3 +478,4 @@ Authors
=======
Thomas Woerner
Rafael Jeffman

View File

@@ -24,7 +24,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -30,7 +30,11 @@ Features
* Modules for hbacsvcgroup management
* Modules for host management
* Modules for hostgroup management
* Modules for idoverridegroup management
* Modules for idoverrideuser management
* Modules for idp management
* Modules for idrange management
* Modules for idview management
* Modules for location management
* Modules for netgroup management
* Modules for permission management
@@ -69,7 +73,7 @@ Requirements
------------
**Controller**
* Ansible version: 2.8+ (ansible-freeipa is an Ansible Collection)
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)
@@ -129,18 +133,8 @@ This command will get the whole collection from galaxy:
ansible-galaxy collection install freeipa.ansible_freeipa
```
Installing collections using the ansible-galaxy command is only supported with ansible 2.9+.
The mazer tool can be used for to install the collection for ansible 2.8:
```bash
mazer install freeipa.ansible_freeipa
```
Ansible galaxy does not support the use of dash ('-') in a name and is automatically replacing this with an underscore ('\_'). Therefore the name is `ansible_freeipa`. The ansible_freeipa collection will be placed in the directory `~/.ansible/collections/ansible_collections/freeipa/ansible_freeipa` where it will be automatically be found for this user.
The needed adaptions of collection prefixes for `modules` and `module_utils` will be done with ansible-freeipa release `0.1.6` for galaxy.
Ansible inventory file
----------------------
@@ -450,7 +444,11 @@ Modules in plugin/modules
* [ipahbacsvcgroup](README-hbacsvcgroup.md)
* [ipahost](README-host.md)
* [ipahostgroup](README-hostgroup.md)
* [idoverridegroup](README-idoverridegroup.md)
* [idoverrideuser](README-idoverrideuser.md)
* [idp](README-idp.md)
* [idrange](README-idrange.md)
* [idview](README-idview.md)
* [ipalocation](README-location.md)
* [ipanetgroup](README-netgroup.md)
* [ipapermission](README-permission.md)

View File

@@ -1,2 +1,2 @@
---
requires_ansible: ">=2.9"
requires_ansible: ">=2.13"

View File

@@ -0,0 +1,14 @@
---
- name: Managed automount maps
hosts: ipaserver
become: false
gather_facts: false
tasks:
- name: Playbook to add an indirect automount map
ipaautomountmap:
ipaadmin_password: SomeADMINpassword
name: auto.indirect
location: DMZ
parent: auto.DMZ
mount: dmz_indirect

View File

@@ -0,0 +1,13 @@
---
- name: Playbook to manage idoverridegroup
hosts: ipaserver
become: no
tasks:
- name: Ensure idoverridegroup test_group is absent in idview test_idview.
ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
continue: true
state: absent

View File

@@ -0,0 +1,11 @@
---
- name: Playbook to manage idoverridegroup
hosts: ipaserver
become: no
tasks:
- name: Ensure idoverridegroup test_group is present in idview test_idview.
ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group

View File

@@ -0,0 +1,13 @@
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is absent in idview test_idview
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
continue: true
state: absent

View File

@@ -0,0 +1,15 @@
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user certificate member is absent in idview test_idview
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
certificate:
- "{{ lookup('file', 'cert1.b64', rstrip=False) }}"
action: member
state: absent

View File

@@ -0,0 +1,14 @@
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user certificate member is present in idview test_idview
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
certificate:
- "{{ lookup('file', 'cert1.b64', rstrip=False) }}"
action: member

View File

@@ -0,0 +1,11 @@
---
- name: Playbook to manage idoverrideuser
hosts: ipaserver
become: false
tasks:
- name: Ensure test user test_user is present in idview test_idview.
ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user

View File

@@ -0,0 +1,11 @@
---
- name: Idp absent example
hosts: ipaserver
become: no
tasks:
- name: Ensure github idp my-github-idp is absent
ipaidp:
ipaadmin_password: SomeADMINpassword
name: my-github-idp
state: absent

View File

@@ -0,0 +1,12 @@
---
- name: Idp present example
hosts: ipaserver
become: no
tasks:
- name: Ensure github idp my-github-idp is present
ipaidp:
ipaadmin_password: SomeADMINpassword
name: my-github-idp
provider: github
client_id: my-github-client-id

View File

@@ -0,0 +1,11 @@
---
- name: Idview absent example
hosts: ipaserver
become: no
tasks:
- name: Ensure idview test_idview is absent
ipaidview:
ipaadmin_password: SomeADMINpassword
name: test_idview
state: absent

View File

@@ -0,0 +1,12 @@
---
- name: Idview host member applied example
hosts: ipaserver
become: no
tasks:
- name: Ensure host testhost.example.com is applied to idview test_idview
ipaidview:
ipaadmin_password: SomeADMINpassword
name: test_idview
host: testhost.example.com
action: member

View File

@@ -0,0 +1,13 @@
---
- name: Idview host member unapplied example
hosts: ipaserver
become: no
tasks:
- name: Ensure host testhost.example.com is not applied to idview test_idview
ipaidview:
ipaadmin_password: SomeADMINpassword
name: test_idview
host: testhost.example.com
action: member
state: absent

View File

@@ -0,0 +1,10 @@
---
- name: Idview present example
hosts: ipaserver
become: no
tasks:
- name: Ensure idview test_idview is present
ipaidview:
ipaadmin_password: SomeADMINpassword
name: test_idview

View File

@@ -0,0 +1,12 @@
---
- name: Playbook to handle users
hosts: ipaserver
become: true
tasks:
- name: Create user associated with an external IdP
ipauser:
ipaadmin_password: SomeADMINpassword
name: idpuser
idp: keycloak
idp_user_id: idpuser@exemple.com

View File

@@ -0,0 +1,17 @@
---
- name: Plabook to handle users
hosts: ipaserver
become: false
gather_facts: false
tasks:
- name: Ensure user 'smbuser' is present with smb attributes
ipauser:
ipaadmin_password: SomeADMINpassword
name: smbuser
first: SMB
last: User
smb_logon_script: N:\logonscripts\startup
smb_profile_path: \\server\profiles\some_profile
smb_home_dir: \\users\home\smbuser
smb_home_drive: "U:"

View File

@@ -30,7 +30,7 @@ __all__ = ["gssapi", "netaddr", "api", "ipalib_errors", "Env",
"kinit_password", "kinit_keytab", "run", "DN", "VERSION",
"paths", "tasks", "get_credentials_if_valid", "Encoding",
"DNSName", "getargspec", "certificate_loader",
"write_certificate_list"]
"write_certificate_list", "boolean", "template_str"]
import os
# ansible-freeipa requires locale to be C, IPA requires utf-8.
@@ -42,6 +42,7 @@ import tempfile
import shutil
import socket
import base64
import ast
from datetime import datetime
from contextlib import contextmanager
from ansible.module_utils.basic import AnsibleModule
@@ -49,6 +50,7 @@ from ansible.module_utils._text import to_text
from ansible.module_utils.common.text.converters import jsonify
from ansible.module_utils import six
from ansible.module_utils.common._collections_compat import Mapping
from ansible.module_utils.parsing.convert_bool import boolean
# Import getargspec from inspect or provide own getargspec for
# Python 2 compatibility with Python 3.11+.
@@ -88,6 +90,7 @@ try:
except ImportError:
from ipapython.ipautil import kinit_password, kinit_keytab
from ipapython.ipautil import run
from ipapython.ipautil import template_str
from ipapython.dn import DN
from ipapython.version import VERSION
from ipaplatform.paths import paths
@@ -224,7 +227,7 @@ def temp_kdestroy(ccache_dir, ccache_name):
"""Destroy temporary ticket and remove temporary ccache."""
if ccache_name is not None:
run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False)
del os.environ['KRB5CCNAME']
os.environ.pop('KRB5CCNAME', None)
if ccache_dir is not None:
shutil.rmtree(ccache_dir, ignore_errors=True)
@@ -1168,6 +1171,45 @@ class IPAAnsibleModule(AnsibleModule):
"""
return api_check_param(command, name)
def ipa_command_invalid_param_choices(self, command, name, value):
"""
Return invalid parameter choices for IPA command.
Parameters
----------
command: string
The IPA API command to test.
name: string
The parameter name to check.
value: string
The parameter value to verify.
"""
if command not in api.Command:
self.fail_json(msg="The command '%s' does not exist." % command)
if name not in api.Command[command].params:
self.fail_json(msg="The command '%s' does not have a parameter "
"named '%s'." % (command, name))
if not hasattr(api.Command[command].params[name], "cli_metavar"):
self.fail_json(msg="The parameter '%s' of the command '%s' does "
"not have choices." % (name, command))
# For IPA 4.6 (RHEL-7):
# - krbprincipalauthind in host_add does not have choices defined
# - krbprincipalauthind in service_add does not have choices defined
#
# api.Command[command].params[name].cli_metavar returns "STR" and
# ast.literal_eval failes with a ValueError "malformed string".
#
# There is no way to verify that the given values are valid or not in
# this case. The check is done later on while applying the change
# with host_add, host_mod, service_add and service_mod.
try:
_choices = ast.literal_eval(
api.Command[command].params[name].cli_metavar)
except ValueError:
return None
return (set(value or []) - set([""])) - set(_choices)
@staticmethod
def ipa_check_version(oper, requested_version):
"""

View File

@@ -121,8 +121,7 @@ class AutomountKey(IPAAnsibleModule):
resp = self.ipa_command("automountkey_show", location, args)
except ipalib_errors.NotFound:
return None
else:
return resp.get("result")
return resp.get("result")
def check_ipa_params(self):
invalid = []

View File

@@ -92,8 +92,7 @@ class AutomountLocation(IPAAnsibleModule):
)
except ipalib_errors.NotFound:
return None
else:
return response.get("result", None)
return response.get("result", None)
def check_ipa_params(self):
if len(self.params_get("name")) == 0:

View File

@@ -37,6 +37,7 @@ module: ipaautomountmap
author:
- Chris Procter (@chr15p)
- Thomas Woerner (@t-woerner)
- Rafael Jeffman (@rjeffman)
short_description: Manage FreeIPA autommount map
description:
- Add, delete, and modify an IPA automount map
@@ -59,6 +60,16 @@ options:
type: str
aliases: ["description"]
required: false
parentmap:
description: |
Parent map of the indirect map. Can only be used when creating
new maps.
type: str
required: false
mount:
description: Indirect map mount point, relative to parent map.
type: str
required: false
state:
description: State to ensure
type: str
@@ -75,6 +86,14 @@ EXAMPLES = '''
location: DMZ
desc: "this is a map for servers in the DMZ"
- name: ensure indirect map exists
ipaautomountmap:
ipaadmin_password: SomeADMINpassword
name: auto.INDIRECT
location: DMZ
parentmap: auto.DMZ
mount: indirect
- name: remove a map named auto.DMZ in location DMZ if it exists
ipaautomountmap:
ipaadmin_password: SomeADMINpassword
@@ -107,8 +126,36 @@ class AutomountMap(IPAAnsibleModule):
)
except Exception: # pylint: disable=broad-except
return None
else:
return response["result"]
return response["result"]
def get_indirect_map_keys(self, location, name):
"""Check if 'name' is an indirect map for 'parentmap'."""
try:
maps = self.ipa_command("automountmap_find", location, {})
except Exception: # pylint: disable=broad-except
return []
result = []
for check_map in maps.get("result", []):
_mapname = check_map['automountmapname'][0]
keys = self.ipa_command(
"automountkey_find",
location,
{
"automountmapautomountmapname": _mapname,
"all": True
}
)
cmp_value = (
name if _mapname == "auto.master" else "ldap:{0}".format(name)
)
result.extend([
(location, _mapname, key.get("automountkey")[0])
for key in keys.get("result", [])
for mount_info in key.get("automountinformation", [])
if cmp_value in mount_info
])
return result
def check_ipa_params(self):
invalid = []
@@ -118,15 +165,27 @@ class AutomountMap(IPAAnsibleModule):
if len(name) != 1:
self.fail_json(msg="Exactly one name must be provided for"
" 'state: present'.")
mount = self.params_get("mount") or False
parentmap = self.params_get("parentmap")
if parentmap:
if not mount:
self.fail_json(
msg="Must provide 'mount' parameter for indirect map."
)
elif parentmap != "auto.master" and mount[0] == "/":
self.fail_json(
msg="mount point is relative to parent map, "
"cannot begin with '/'"
)
if state == "absent":
if len(name) == 0:
self.fail_json(msg="At least one 'name' must be provided for"
" 'state: absent'")
invalid = ["desc"]
invalid = ["desc", "parentmap", "mount"]
self.params_fail_used_invalid(invalid, state)
def get_args(self, mapname, desc):
def get_args(self, mapname, desc, parentmap, mount):
# automountmapname is required for all automountmap operations.
if not mapname:
self.fail_json(msg="automountmapname cannot be None or empty.")
@@ -134,6 +193,11 @@ class AutomountMap(IPAAnsibleModule):
# An empty string is valid and will clear the attribute.
if desc is not None:
_args["description"] = desc
# indirect map attributes
if parentmap is not None:
_args["parentmap"] = parentmap
if mount is not None:
_args["key"] = mount
return _args
def define_ipa_commands(self):
@@ -141,28 +205,102 @@ class AutomountMap(IPAAnsibleModule):
state = self.params_get("state")
location = self.params_get("location")
desc = self.params_get("desc")
mount = self.params_get("mount")
parentmap = self.params_get("parentmap")
for mapname in name:
automountmap = self.get_automountmap(location, mapname)
is_indirect_map = any([parentmap, mount])
if state == "present":
args = self.get_args(mapname, desc)
args = self.get_args(mapname, desc, parentmap, mount)
if automountmap is None:
self.commands.append([location, "automountmap_add", args])
if is_indirect_map:
if (
parentmap and
self.get_automountmap(location, parentmap) is None
):
self.fail_json(msg="Parent map does not exist.")
self.commands.append(
[location, "automountmap_add_indirect", args]
)
else:
self.commands.append(
[location, "automountmap_add", args]
)
else:
if not compare_args_ipa(self, args, automountmap):
has_changes = not compare_args_ipa(
self, args, automountmap, ['parentmap', 'key']
)
if is_indirect_map:
map_config = (
location, parentmap or "auto.master", mount
)
indirects = self.get_indirect_map_keys(
location, mapname
)
if map_config not in indirects or has_changes:
self.fail_json(
msg="Indirect maps can only be created, "
"not modified."
)
elif has_changes:
self.commands.append(
[location, "automountmap_mod", args]
)
if state == "absent":
elif state == "absent":
def find_keys(parent_loc, parent_map, parent_key):
return self.ipa_command(
"automountkey_show",
parent_loc,
{
"automountmapautomountmapname": parent_map,
"automountkey": parent_key,
}
).get("result")
if automountmap is not None:
indirects = self.get_indirect_map_keys(location, mapname)
# Remove indirect map configurations for this map
self.commands.extend([
(
ploc,
"automountkey_del",
{
"automountmapautomountmapname": pmap,
"automountkey": pkey,
}
)
for ploc, pmap, pkey in indirects
if find_keys(ploc, pmap, pkey)
])
# Remove map
self.commands.append([
location,
"automountmap_del",
{"automountmapname": [mapname]}
])
# ensure commands are unique and automountkey commands are
# executed first in the list
def hashable_dict(dictionaire):
return tuple(
(k, tuple(v) if isinstance(v, (list, tuple)) else v)
for k, v in dictionaire.items()
)
cmds = [
(name, cmd, hashable_dict(args))
for name, cmd, args in self.commands
]
self.commands = [
(name, cmd, dict(args))
for name, cmd, args in
sorted(set(cmds), key=lambda cmd: cmd[1])
]
def main():
ipa_module = AutomountMap(
@@ -184,6 +322,10 @@ def main():
required=False,
default=None
),
parentmap=dict(
type="str", required=False, default=None
),
mount=dict(type="str", required=False, default=None),
),
)
changed = False

View File

@@ -160,7 +160,8 @@ options:
required: false
type: list
elements: str
choices: ["password", "radius", "otp", "disabled", ""]
choices: ["password", "radius", "otp", "pkinit", "hardened", "idp",
"disabled", ""]
aliases: ["ipauserauthtype"]
ca_renewal_master_server:
description: Renewal master for IPA certificate authority.
@@ -357,8 +358,7 @@ def get_netbios_name(module):
_result = module.ipa_command_no_name("trustconfig_show", {"all": True})
except Exception: # pylint: disable=broad-except
return None
else:
return _result["result"]["ipantflatname"][0]
return _result["result"]["ipantflatname"][0]
def is_enable_sid(module):
@@ -425,6 +425,7 @@ def main():
choices=["MS-PAC", "PAD", "nfs:NONE", ""]),
user_auth_type=dict(type="list", elements="str", required=False,
choices=["password", "radius", "otp",
"pkinit", "hardened", "idp",
"disabled", ""],
aliases=["ipauserauthtype"]),
ca_renewal_master_server=dict(type="str", required=False),
@@ -475,7 +476,7 @@ def main():
params = {}
for x in field_map:
val = ansible_module.params_get(
x, allow_empty_string=(x in allow_empty_string))
x, allow_empty_string=x in allow_empty_string)
if val is not None:
params[field_map.get(x, x)] = val
@@ -525,6 +526,15 @@ def main():
result = config_show(ansible_module)
if params:
# Verify ipauserauthtype(s)
if "ipauserauthtype" in params and params["ipauserauthtype"]:
_invalid = ansible_module.ipa_command_invalid_param_choices(
"config_mod", "ipauserauthtype", params["ipauserauthtype"])
if _invalid:
ansible_module.fail_json(
msg="The use of userauthtype '%s' is not "
"supported by your IPA version" % "','".join(_invalid))
enable_sid = params.get("enable_sid")
sid_is_enabled = has_enable_sid and is_enable_sid(ansible_module)
@@ -609,7 +619,7 @@ def main():
# boolean values, so we need to convert it to str
# for comparison.
# See: https://github.com/freeipa/freeipa/pull/6294
exit_args[k] = (str(value[0]).upper() == "TRUE")
exit_args[k] = str(value[0]).upper() == "TRUE"
else:
if arg_type not in type_map:
raise ValueError(

View File

@@ -134,8 +134,7 @@ def find_delegation(module, name):
except Exception: # pylint: disable=broad-except
# An exception is raised if delegation name is not found.
return None
else:
return _result["result"]
return _result["result"]
def gen_args(permission, attribute, membergroup, group):

View File

@@ -258,7 +258,7 @@ def main():
invalid = [
"forwarders", "forwardpolicy", "skip_overlap_check", "permission"
]
wants_enable = (state == "enabled")
wants_enable = state == "enabled"
if operation == "del":
invalid = [

View File

@@ -1453,7 +1453,7 @@ def define_commands_for_present_state(module, zone_name, entry, res_find):
else:
# Create reverse records for existing records
for ipv in ['a', 'aaaa']:
record = ('%srecord' % ipv)
record = '%srecord' % ipv
if record in args and ('%s_extra_create_reverse' % ipv) in args:
cmds = create_reverse_ip_record(
module, zone_name, name, args[record])

View File

@@ -186,7 +186,17 @@ def find_hbacrule(module, name):
module.fail_json(
msg="There is more than one hbacrule '%s'" % (name))
elif len(_result["result"]) == 1:
return _result["result"][0]
res = _result["result"][0]
# hbacsvcgroup names are converted to lower case while creation with
# hbacsvcgroup_add.
# The hbacsvcgroup for sudo is builtin with the name "Sudo" though.
# This breaks the lower case comparison. Therefore all
# memberservice_hbacsvcgroup items are converted to lower case if
# "Sudo" is in the list.
_member = "memberservice_hbacsvcgroup"
if _member in res and "Sudo" in res[_member]:
res[_member] = [item.lower() for item in res[_member]]
return res
return None

View File

@@ -146,21 +146,6 @@ def gen_member_args(hbacsvc):
return _args
# pylint: disable=unused-argument
def result_handler(module, result, command, name, args, errors):
# Get all errors
# All "already a member" and "not a member" failures in the
# result are ignored. All others are reported.
if "failed" in result and "member" in result["failed"]:
failed = result["failed"]["member"]
for member_type in failed:
for member, failure in failed[member_type]:
if "already a member" not in failure \
and "not a member" not in failure:
errors.append("%s: %s %s: %s" % (
command, member_type, member, failure))
def main():
ansible_module = IPAAnsibleModule(
argument_spec=dict(
@@ -303,7 +288,8 @@ def main():
}])
# Execute commands
changed = ansible_module.execute_ipa_commands(commands, result_handler)
changed = ansible_module.execute_ipa_commands(
commands, fail_on_member_errors=True)
# Done

View File

@@ -63,7 +63,7 @@ options:
type: str
required: false
location:
description: Host location (e.g. "Lab 2")
description: Host physical location hist (e.g. "Lab 2")
type: str
aliases: ["ns_host_location"]
required: false
@@ -184,7 +184,7 @@ options:
type: list
elements: str
aliases: ["krbprincipalauthind"]
choices: ["radius", "otp", "pkinit", "hardened", ""]
choices: ["radius", "otp", "pkinit", "hardened", "idp", ""]
required: false
requires_pre_auth:
description: Pre-authentication is required for the service
@@ -356,7 +356,7 @@ options:
type: list
elements: str
aliases: ["krbprincipalauthind"]
choices: ["radius", "otp", "pkinit", "hardened", ""]
choices: ["radius", "otp", "pkinit", "hardened", "idp", ""]
required: false
requires_pre_auth:
description: Pre-authentication is required for the service
@@ -667,6 +667,15 @@ def check_parameters( # pylint: disable=unused-argument
module.params_fail_used_invalid(invalid, state, action)
def check_authind(module, auth_ind):
_invalid = module.ipa_command_invalid_param_choices(
"host_add", "krbprincipalauthind", auth_ind)
if _invalid:
module.fail_json(
msg="The use of krbprincipalauthind '%s' is not supported "
"by your IPA version" % "','".join(_invalid))
# pylint: disable=unused-argument
def result_handler(module, result, command, name, args, errors, exit_args,
single_host):
@@ -776,7 +785,8 @@ def main():
default=None),
auth_ind=dict(type='list', elements="str",
aliases=["krbprincipalauthind"], default=None,
choices=['radius', 'otp', 'pkinit', 'hardened', '']),
choices=["radius", "otp", "pkinit", "hardened", "idp",
""]),
requires_pre_auth=dict(type="bool", aliases=["ipakrbrequirespreauth"],
default=None),
ok_as_delegate=dict(type="bool", aliases=["ipakrbokasdelegate"],
@@ -919,6 +929,8 @@ def main():
# Check version specific settings
check_authind(ansible_module, auth_ind)
server_realm = ansible_module.ipa_get_realm()
commands = []
@@ -961,6 +973,7 @@ def main():
sshpubkey = host.get("sshpubkey")
userclass = host.get("userclass")
auth_ind = host.get("auth_ind")
check_authind(ansible_module, auth_ind)
requires_pre_auth = host.get("requires_pre_auth")
ok_as_delegate = host.get("ok_as_delegate")
ok_to_auth_as_delegate = host.get("ok_to_auth_as_delegate")

View File

@@ -0,0 +1,354 @@
# -*- coding: utf-8 -*-
# Authors:
# Thomas Woerner <twoerner@redhat.com>
#
# Copyright (C) 2023 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, exither 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"],
}
# No rename support: 'ID overrides cannot be renamed'
# ipaserver/plugins/idviews.py:baseidoverride_mod:pre_callback
DOCUMENTATION = """
---
module: ipaidoverridegroup
short_description: Manage FreeIPA idoverridegroup
description: Manage FreeIPA idoverridegroups
extends_documentation_fragment:
- ipamodule_base_docs
options:
idview:
description: The idoverridegroup idview string.
type: str
required: true
aliases: ["idviewcn"]
anchor:
description: The list of anchors to override
type: list
elements: str
required: true
aliases: ["ipaanchoruuid"]
description:
description: Description
type: str
required: False
aliases: ["desc"]
name:
description: Group name
type: str
required: False
aliases: ["group_name", "cn"]
gid:
description: Group ID Number (int or "")
type: str
required: False
aliases: ["gidnumber"]
fallback_to_ldap:
description: |
Allow falling back to AD DC LDAP when resolving AD trusted objects.
For two-way trusts only.
required: False
type: bool
delete_continue:
description: |
Continuous mode. Don't stop on errors.
Valid only if `state` is `absent`.
required: false
type: bool
aliases: ["continue"]
state:
description: The state to ensure.
choices: ["present", "absent"]
default: present
type: str
author:
- Thomas Woerner (@t-woerner)
"""
EXAMPLES = """
# Ensure test group test_group is present in idview test_idview
- ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
# Ensure test group test_group is present in idview test_idview with
# description
- ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
description: "test_group description"
# Ensure test group test_group is present in idview test_idview without
# description
- ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
description: ""
# Ensure test group test_group is present in idview test_idview with internal
# name test_123_group
- ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
name: test_123_group
# Ensure test group test_group is present in idview test_idview without
# internal name
- ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
name: ""
# Ensure test group test_group is present in idview test_idview with gid 20001
- ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
gid: 20001
# Ensure test group test_group is present in idview test_idview without gid
- ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
gid: ""
# Ensure test group test_group is absent in idview test_idview
- ipaidoverridegroup:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_group
continue: true
state: absent
"""
RETURN = """
"""
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa
from ansible.module_utils import six
if six.PY3:
unicode = str
def find_idoverridegroup(module, idview, anchor):
"""Find if a idoverridegroup with the given name already exist."""
try:
_result = module.ipa_command("idoverridegroup_show", idview,
{"ipaanchoruuid": anchor,
"all": True})
except Exception: # pylint: disable=broad-except
# An exception is raised if idoverridegroup anchor is not found.
return None
return _result["result"]
def gen_args(anchor, description, name, gid):
# fallback_to_ldap is only a runtime tuning parameter
_args = {}
if anchor is not None:
_args["ipaanchoruuid"] = anchor
if description is not None:
_args["description"] = description
if name is not None:
_args["cn"] = name
if gid is not None:
_args["gidnumber"] = gid
return _args
def gen_args_runtime(fallback_to_ldap):
_args = {}
if fallback_to_ldap is not None:
_args["fallback_to_ldap"] = fallback_to_ldap
return _args
def merge_dicts(dict1, dict2):
ret = dict1.copy()
ret.update(dict2)
return ret
def main():
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# general
idview=dict(type="str", required=True, aliases=["idviewcn"]),
anchor=dict(type="list", elements="str", required=True,
aliases=["ipaanchoruuid"]),
# present
description=dict(type="str", required=False, aliases=["desc"]),
name=dict(type="str", required=False,
aliases=["group_name", "cn"]),
gid=dict(type="str", required=False, aliases=["gidnumber"]),
# runtime flags
fallback_to_ldap=dict(type="bool", required=False),
# absent
delete_continue=dict(type="bool", required=False,
aliases=['continue'], default=None),
# No rename support: 'ID overrides cannot be renamed'
# ipaserver/plugins/idviews.py:baseidoverride_mod:pre_callback
# state
state=dict(type="str", default="present",
choices=["present", "absent"]),
),
supports_check_mode=True,
)
ansible_module._ansible_debug = True
# Get parameters
# general
idview = ansible_module.params_get("idview")
anchors = ansible_module.params_get("anchor")
# present
description = ansible_module.params_get("description")
name = ansible_module.params_get("name")
gid = ansible_module.params_get("gid")
# runtime flags
fallback_to_ldap = ansible_module.params_get("fallback_to_ldap")
# absent
delete_continue = ansible_module.params_get("delete_continue")
# state
state = ansible_module.params_get("state")
# Check parameters
invalid = []
if state == "present":
if len(anchors) != 1:
ansible_module.fail_json(
msg="Only one idoverridegroup can be added at a time.")
invalid = ["delete_continue"]
if state == "absent":
if len(anchors) < 1:
ansible_module.fail_json(msg="No name given.")
invalid = ["description", "name", "gid"]
ansible_module.params_fail_used_invalid(invalid, state)
# Ensure parameter values are valid and have proper type.
def int_or_empty_param(value, param):
if value is not None and value != "":
try:
value = int(value)
except ValueError:
ansible_module.fail_json(
msg="Invalid value '%s' for argument '%s'" % (value, param)
)
return value
gid = int_or_empty_param(gid, "gid")
# Init
changed = False
exit_args = {}
# Connect to IPA API
with ansible_module.ipa_connect():
runtime_args = gen_args_runtime(fallback_to_ldap)
commands = []
for anchor in anchors:
# Make sure idoverridegroup exists
res_find = find_idoverridegroup(ansible_module, idview, anchor)
# Create command
if state == "present":
# Generate args
args = gen_args(anchor, description, name, gid)
# fallback_to_ldap is only a runtime tuning parameter
all_args = merge_dicts(args, runtime_args)
# Found the idoverridegroup
if res_find is not None:
# For idempotency: Remove empty sshpubkey list if
# there are no sshpubkey in the found entry.
if "ipasshpubkey" in args and \
len(args["ipasshpubkey"]) < 1 and \
"ipasshpubkey" not in res_find:
del args["ipasshpubkey"]
# For all settings is args, check if there are
# different settings in the find result.
# If yes: modify
if not compare_args_ipa(ansible_module, args,
res_find):
commands.append([idview, "idoverridegroup_mod",
all_args])
else:
commands.append([idview, "idoverridegroup_add",
all_args])
elif state == "absent":
if res_find is not None:
commands.append(
[idview, "idoverridegroup_del",
merge_dicts(
{
"ipaanchoruuid": anchor,
"continue": delete_continue or False
},
runtime_args
)]
)
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Execute commands
changed = ansible_module.execute_ipa_commands(commands)
# Done
ansible_module.exit_json(changed=changed, **exit_args)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,631 @@
# -*- coding: utf-8 -*-
# Authors:
# Thomas Woerner <twoerner@redhat.com>
#
# Copyright (C) 2023 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"],
}
# No rename support: 'ID overrides cannot be renamed'
# ipaserver/plugins/idviews.py:baseidoverride_mod:pre_callback
DOCUMENTATION = """
---
module: ipaidoverrideuser
short_description: Manage FreeIPA idoverrideuser
description: Manage FreeIPA idoverrideuser and idoverrideuser members
extends_documentation_fragment:
- ipamodule_base_docs
options:
idview:
description: The idoverrideuser idview string.
type: str
required: true
aliases: ["idviewcn"]
anchor:
description: The list of anchors to override
type: list
elements: str
required: true
aliases: ["ipaanchoruuid"]
description:
description: Description
type: str
required: False
aliases: ["desc"]
name:
description: The user (internally uid)
type: str
required: False
aliases: ["login"]
uid:
description: User ID Number (int or "")
type: str
required: False
aliases: ["uidnumber"]
gecos:
description: GECOS
required: False
type: str
gidnumber:
description: Group ID Number (int or "")
required: False
type: str
homedir:
description: Home directory
type: str
required: False
aliases: ["homedirectory"]
shell:
description: Login shell
type: str
required: False
aliases: ["loginshell"]
sshpubkey:
description: List of SSH public keys
type: list
element: str
required: False
aliases: ["ipasshpubkey"]
certificate:
description: List of Base-64 encoded user certificates
type: list
elements: str
required: False
aliases: ["usercertificate"]
fallback_to_ldap:
description: |
Allow falling back to AD DC LDAP when resolving AD trusted objects.
For two-way trusts only.
required: False
type: bool
delete_continue:
description: |
Continuous mode. Don't stop on errors.
Valid only if `state` is `absent`.
required: false
type: bool
aliases: ["continue"]
nomembers:
description: |
Suppress processing of membership attributes.
Valid only if `state` is `absent`.
type: str
required: False
aliases: ["no_members"]
action:
description: Work on idoverrideuser or member level.
choices: ["idoverrideuser", "member"]
default: idoverrideuser
type: str
state:
description: The state to ensure.
choices: ["present", "absent"]
default: present
type: str
author:
- Thomas Woerner (@t-woerner)
"""
EXAMPLES = """
# Ensure test user test_user is present in idview test_idview
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
# Ensure test user test_user is present in idview test_idview with description
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
description: "test_user description"
# Ensure test user test_user is present in idview test_idview without
# description
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
description: ""
# Ensure test user test_user is present in idview test_idview with internal
# name test_123_user
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
name: test_123_user
# Ensure test user test_user is present in idview test_idview without internal
# name
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
name: ""
# Ensure test user test_user is present in idview test_idview with uid 20001
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
uid: 20001
# Ensure test user test_user is present in idview test_idview without uid
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
uid: ""
# Ensure test user test_user is present in idview test_idview with gecos
# "Gecos Test"
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
gecos: Gecos Test
# Ensure test user test_user is present in idview test_idview without gecos
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
gecos: ""
# Ensure test user test_user is present in idview test_idview with gidnumber
# 20001
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
gidnumber: 20001
# Ensure test user test_user is present in idview test_idview without
# gidnumber
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
gidnumber: ""
# Ensure test user test_user is present in idview test_idview with homedir
# /Users
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
homedir: /Users
# Ensure test user test_user is present in idview test_idview without homedir
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
homedir: ""
# Ensure test user test_user is present in idview test_idview with shell
# /bin/someshell
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
shell: /bin/someshell
# Ensure test user test_user is present in idview test_idview without shell
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
shell: ""
# Ensure test user test_user is present in idview test_idview with sshpubkey
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
sshpubkey:
- ssh-rsa AAAAB3NzaC1yc2EAAADAQABAAABgQCqmVDpEX5gnSjKuv97Ay ...
# Ensure test user test_user is present in idview test_idview without
# sshpubkey
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
sshpubkey: []
# Ensure test user test_user is present in idview test_idview with 1
# certificate
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
certificate:
- "{{ lookup('file', 'cert1.b64', rstrip=False) }}"
# Ensure test user test_user is present in idview test_idview with 3
# certificate members
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
certificate:
- "{{ lookup('file', 'cert1.b64', rstrip=False) }}"
- "{{ lookup('file', 'cert2.b64', rstrip=False) }}"
- "{{ lookup('file', 'cert3.b64', rstrip=False) }}"
action: member
# Ensure test user test_user is present in idview test_idview without
# 2 certificate members
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
certificate:
- "{{ lookup('file', 'cert2.b64', rstrip=False) }}"
- "{{ lookup('file', 'cert3.b64', rstrip=False) }}"
action: member
state: absent
# Ensure test user test_user is present in idview test_idview without
# certificates
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
certificate: []
# Ensure test user test_user is absent in idview test_idview
- ipaidoverrideuser:
ipaadmin_password: SomeADMINpassword
idview: test_idview
anchor: test_user
continue: true
state: absent
"""
RETURN = """
"""
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, gen_add_list, \
gen_intersection_list, encode_certificate
from ansible.module_utils import six
if six.PY3:
unicode = str
def find_idoverrideuser(module, idview, anchor):
"""Find if a idoverrideuser with the given name already exist."""
try:
_result = module.ipa_command("idoverrideuser_show", idview,
{"ipaanchoruuid": anchor,
"all": True})
except Exception: # pylint: disable=broad-except
# An exception is raised if idoverrideuser anchor is not found.
return None
_res = _result["result"]
certs = _res.get("usercertificate")
if certs is not None:
_res["usercertificate"] = [encode_certificate(cert) for cert in certs]
return _res
def gen_args(anchor, description, name, uid, gecos, gidnumber, homedir, shell,
sshpubkey):
# fallback_to_ldap and nomembers are only runtime tuning parameters
_args = {}
if anchor is not None:
_args["ipaanchoruuid"] = anchor
if description is not None:
_args["description"] = description
if name is not None:
_args["uid"] = name
if uid is not None:
_args["uidnumber"] = uid
if gecos is not None:
_args["gecos"] = gecos
if gidnumber is not None:
_args["gidnumber"] = gidnumber
if homedir is not None:
_args["homedirectory"] = homedir
if shell is not None:
_args["loginshell"] = shell
if sshpubkey is not None:
_args["ipasshpubkey"] = sshpubkey
return _args
def gen_args_runtime(fallback_to_ldap, nomembers):
_args = {}
if fallback_to_ldap is not None:
_args["fallback_to_ldap"] = fallback_to_ldap
if nomembers is not None:
_args["no_members"] = nomembers
return _args
def gen_member_args(certificate):
_args = {}
if certificate is not None:
_args["usercertificate"] = certificate
return _args
def merge_dicts(dict1, dict2):
ret = dict1.copy()
ret.update(dict2)
return ret
def main():
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# general
idview=dict(type="str", required=True, aliases=["idviewcn"]),
anchor=dict(type="list", elements="str", required=True,
aliases=["ipaanchoruuid"]),
# present
description=dict(type="str", required=False, aliases=["desc"]),
name=dict(type="str", required=False, aliases=["login"]),
uid=dict(type="str", required=False, aliases=["uidnumber"]),
gecos=dict(type="str", required=False),
gidnumber=dict(type="str", required=False),
homedir=dict(type="str", required=False,
aliases=["homedirectory"]),
shell=dict(type="str", required=False, aliases=["loginshell"]),
sshpubkey=dict(type="list", elements="str", required=False,
aliases=["ipasshpubkey"]),
certificate=dict(type="list", elements="str", required=False,
aliases=["usercertificate"]),
fallback_to_ldap=dict(type="bool", required=False),
nomembers=dict(type="bool", required=False,
aliases=["no_members"]),
# absent
delete_continue=dict(type="bool", required=False,
aliases=['continue'], default=None),
# No rename support: 'ID overrides cannot be renamed'
# ipaserver/plugins/idviews.py:baseidoverride_mod:pre_callback
# action
action=dict(type="str", default="idoverrideuser",
choices=["member", "idoverrideuser"]),
# state
state=dict(type="str", default="present",
choices=["present", "absent"]),
),
supports_check_mode=True,
)
ansible_module._ansible_debug = True
# Get parameters
# general
idview = ansible_module.params_get("idview")
anchors = ansible_module.params_get("anchor")
# present
description = ansible_module.params_get("description")
name = ansible_module.params_get("name")
uid = ansible_module.params_get("uid")
gecos = ansible_module.params_get("gecos")
gidnumber = ansible_module.params_get("gidnumber")
homedir = ansible_module.params_get("homedir")
shell = ansible_module.params_get("shell")
sshpubkey = ansible_module.params_get("sshpubkey")
certificate = ansible_module.params_get("certificate")
fallback_to_ldap = ansible_module.params_get("fallback_to_ldap")
nomembers = ansible_module.params_get("nomembers")
action = ansible_module.params_get("action")
# absent
delete_continue = ansible_module.params_get("delete_continue")
# state
state = ansible_module.params_get("state")
# Check parameters
invalid = []
if state == "present":
if len(anchors) != 1:
ansible_module.fail_json(
msg="Only one idoverrideuser can be added at a time.")
invalid = ["delete_continue"]
if action == "member":
invalid += ["description", "name", "uid", "gecos", "gidnumber",
"homedir", "shell", "sshpubkey"]
if state == "absent":
if len(anchors) < 1:
ansible_module.fail_json(msg="No name given.")
invalid = ["description", "name", "uid", "gecos", "gidnumber",
"homedir", "shell", "sshpubkey", "nomembers"]
if action == "idoverrideuser":
invalid += ["certificate"]
ansible_module.params_fail_used_invalid(invalid, state, action)
# Ensure parameter values are valid and have proper type.
def int_or_empty_param(value, param):
if value is not None and value != "":
try:
value = int(value)
except ValueError:
ansible_module.fail_json(
msg="Invalid value '%s' for argument '%s'" % (value, param)
)
return value
uid = int_or_empty_param(uid, "uid")
gidnumber = int_or_empty_param(gidnumber, "gidnumber")
if certificate is not None:
certificate = [cert.strip() for cert in certificate]
# Init
changed = False
exit_args = {}
# Connect to IPA API
with ansible_module.ipa_connect():
runtime_args = gen_args_runtime(fallback_to_ldap, nomembers)
commands = []
for anchor in anchors:
# Make sure idoverrideuser exists
res_find = find_idoverrideuser(ansible_module, idview, anchor)
# add/del lists
certificate_add, certificate_del = [], []
# Create command
if state == "present":
# Generate args
args = gen_args(anchor, description, name, uid, gecos,
gidnumber, homedir, shell, sshpubkey)
# fallback_to_ldap and nomembers are only runtime tuning
# parameters
all_args = merge_dicts(args, runtime_args)
if action == "idoverrideuser":
# Found the idoverrideuser
if res_find is not None:
# For idempotency: Remove empty sshpubkey list if
# there are no sshpubkey in the found entry.
if "ipasshpubkey" in args and \
len(args["ipasshpubkey"]) < 1 and \
"ipasshpubkey" not in res_find:
del args["ipasshpubkey"]
# For all settings is args, check if there are
# different settings in the find result.
# If yes: modify
if not compare_args_ipa(ansible_module, args,
res_find):
commands.append([idview, "idoverrideuser_mod",
all_args])
else:
commands.append([idview, "idoverrideuser_add",
all_args])
res_find = {}
member_args = gen_member_args(certificate)
if not compare_args_ipa(ansible_module, member_args,
res_find):
# Generate addition and removal lists
certificate_add, certificate_del = gen_add_del_lists(
certificate, res_find.get("usercertificate"))
elif action == "member":
if res_find is None:
ansible_module.fail_json(
msg="No idoverrideuser '%s' in idview '%s'" %
(anchor, idview))
# Reduce add lists for certificate
# to new entries only that are not in res_find.
if certificate is not None:
certificate_add = gen_add_list(
certificate, res_find.get("usercertificate"))
elif state == "absent":
if action == "idoverrideuser":
if res_find is not None:
commands.append(
[idview, "idoverrideuser_del",
merge_dicts(
{
"ipaanchoruuid": anchor,
"continue": delete_continue or False
},
runtime_args
)]
)
elif action == "member":
if res_find is None:
ansible_module.fail_json(
msg="No idoverrideuser '%s' in idview '%s'" %
(anchor, idview))
# Reduce del lists of member_host and member_hostgroup,
# to the entries only that are in res_find.
if certificate is not None:
certificate_del = gen_intersection_list(
certificate, res_find.get("usercertificate"))
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Member management
# Add members
if certificate_add:
commands.append([idview, "idoverrideuser_add_cert",
merge_dicts(
{
"ipaanchoruuid": anchor,
"usercertificate": certificate_add
},
runtime_args
)])
# Remove members
if certificate_del:
commands.append([idview, "idoverrideuser_remove_cert",
merge_dicts(
{
"ipaanchoruuid": anchor,
"usercertificate": certificate_del
},
runtime_args
)])
# Execute commands
changed = ansible_module.execute_ipa_commands(commands)
# Done
ansible_module.exit_json(changed=changed, **exit_args)
if __name__ == "__main__":
main()

544
plugins/modules/ipaidp.py Normal file
View File

@@ -0,0 +1,544 @@
# -*- coding: utf-8 -*-
# Authors:
# Thomas Woerner <twoerner@redhat.com>
#
# Copyright (C) 2023 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: ipaidp
short_description: Manage FreeIPA idp
description: Manage FreeIPA idp
extends_documentation_fragment:
- ipamodule_base_docs
options:
name:
description: The list of idp name strings.
required: true
type: list
elements: str
aliases: ["cn"]
auth_uri:
description: OAuth 2.0 authorization endpoint
required: false
type: str
aliases: ["ipaidpauthendpoint"]
dev_auth_uri:
description: Device authorization endpoint
required: false
type: str
aliases: ["ipaidpdevauthendpoint"]
token_uri:
description: Token endpoint
required: false
type: str
aliases: ["ipaidptokenendpoint"]
userinfo_uri:
description: User information endpoint
required: false
type: str
aliases: ["ipaidpuserinfoendpoint"]
keys_uri:
description: JWKS endpoint
required: false
type: str
aliases: ["ipaidpkeysendpoint"]
issuer_url:
description: The Identity Provider OIDC URL
required: false
type: str
aliases: ["ipaidpissuerurl"]
client_id:
description: OAuth 2.0 client identifier
required: false
type: str
aliases: ["ipaidpclientid"]
secret:
description: OAuth 2.0 client secret
required: false
type: str
no_log: true
aliases: ["ipaidpclientsecret"]
scope:
description: OAuth 2.0 scope. Multiple scopes separated by space
required: false
type: str
aliases: ["ipaidpscope"]
idp_user_id:
description: Attribute for user identity in OAuth 2.0 userinfo
required: false
type: str
aliases: ["ipaidpsub"]
provider:
description: |
Pre-defined template string. This provides the provider defaults, which
can be overridden with the other IdP options.
required: false
type: str
choices: ["google","github","microsoft","okta","keycloak"]
aliases: ["ipaidpprovider"]
organization:
description: Organization ID or Realm name for IdP provider templates
required: false
type: str
aliases: ["ipaidporg"]
base_url:
description: Base URL for IdP provider templates
required: false
type: str
aliases: ["ipaidpbaseurl"]
rename:
description: |
New name the Identity Provider server object. Only with state: renamed.
required: false
type: str
aliases: ["new_name"]
delete_continue:
description:
Continuous mode. Don't stop on errors. Valid only if `state` is `absent`.
required: false
type: bool
aliases: ["continue"]
state:
description: The state to ensure.
choices: ["present", "absent", "renamed"]
default: present
type: str
author:
- Thomas Woerner (@t-woerner)
"""
EXAMPLES = """
# Ensure keycloak idp my-keycloak-idp is present
- ipaidp:
ipaadmin_password: SomeADMINpassword
name: my-keycloak-idp
provider: keycloak
organization: main
base_url: keycloak.idm.example.com:8443/auth
client_id: my-client-id
# Ensure google idp my-google-idp is present
- ipaidp:
ipaadmin_password: SomeADMINpassword
name: my-google-idp
auth_uri: https://accounts.google.com/o/oauth2/auth
dev_auth_uri: https://oauth2.googleapis.com/device/code
token_uri: https://oauth2.googleapis.com/token
userinfo_uri: https://openidconnect.googleapis.com/v1/userinfo
client_id: my-client-id
scope: "openid email"
idp_user_id: email
# Ensure google idp my-google-idp is present without using provider
- ipaidp:
ipaadmin_password: SomeADMINpassword
name: my-google-idp
provider: google
client_id: my-google-client-id
# Ensure keycloak idp my-keycloak-idp is absent
- ipaidp:
ipaadmin_password: SomeADMINpassword
name: my-keycloak-idp
delete_continue: true
state: absent
# Ensure idps my-keycloak-idp, my-github-idp and my-google-idp are absent
- ipaidp:
ipaadmin_password: SomeADMINpassword
name:
- my-keycloak-idp
- my-github-idp
- my-google-idp
delete_continue: true
state: absent
"""
RETURN = """
"""
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, template_str
from ansible.module_utils import six
from copy import deepcopy
import string
from itertools import chain
if six.PY3:
unicode = str
# Copy from FreeIPA ipaserver/plugins/idp.py
idp_providers = {
'google': {
'ipaidpauthendpoint':
'https://accounts.google.com/o/oauth2/auth',
'ipaidpdevauthendpoint':
'https://oauth2.googleapis.com/device/code',
'ipaidptokenendpoint':
'https://oauth2.googleapis.com/token',
'ipaidpuserinfoendpoint':
'https://openidconnect.googleapis.com/v1/userinfo',
'ipaidpkeysendpoint':
'https://www.googleapis.com/oauth2/v3/certs',
'ipaidpscope': 'openid email',
'ipaidpsub': 'email'},
'github': {
'ipaidpauthendpoint':
'https://github.com/login/oauth/authorize',
'ipaidpdevauthendpoint':
'https://github.com/login/device/code',
'ipaidptokenendpoint':
'https://github.com/login/oauth/access_token',
'ipaidpuserinfoendpoint':
'https://api.github.com/user',
'ipaidpscope': 'user',
'ipaidpsub': 'login'},
'microsoft': {
'ipaidpauthendpoint':
'https://login.microsoftonline.com/${ipaidporg}/oauth2/v2.0/'
'authorize',
'ipaidpdevauthendpoint':
'https://login.microsoftonline.com/${ipaidporg}/oauth2/v2.0/'
'devicecode',
'ipaidptokenendpoint':
'https://login.microsoftonline.com/${ipaidporg}/oauth2/v2.0/'
'token',
'ipaidpuserinfoendpoint':
'https://graph.microsoft.com/oidc/userinfo',
'ipaidpkeysendpoint':
'https://login.microsoftonline.com/common/discovery/v2.0/keys',
'ipaidpscope': 'openid email',
'ipaidpsub': 'email',
},
'okta': {
'ipaidpauthendpoint':
'https://${ipaidpbaseurl}/oauth2/v1/authorize',
'ipaidpdevauthendpoint':
'https://${ipaidpbaseurl}/oauth2/v1/device/authorize',
'ipaidptokenendpoint':
'https://${ipaidpbaseurl}/oauth2/v1/token',
'ipaidpuserinfoendpoint':
'https://${ipaidpbaseurl}/oauth2/v1/userinfo',
'ipaidpscope': 'openid email',
'ipaidpsub': 'email'},
'keycloak': {
'ipaidpauthendpoint':
'https://${ipaidpbaseurl}/realms/${ipaidporg}/protocol/'
'openid-connect/auth',
'ipaidpdevauthendpoint':
'https://${ipaidpbaseurl}/realms/${ipaidporg}/protocol/'
'openid-connect/auth/device',
'ipaidptokenendpoint':
'https://${ipaidpbaseurl}/realms/${ipaidporg}/protocol/'
'openid-connect/token',
'ipaidpuserinfoendpoint':
'https://${ipaidpbaseurl}/realms/${ipaidporg}/protocol/'
'openid-connect/userinfo',
'ipaidpscope': 'openid email',
'ipaidpsub': 'email'},
}
def find_idp(module, name):
"""Find if a idp with the given name already exist."""
try:
_result = module.ipa_command("idp_show", name, {"all": True})
except Exception: # pylint: disable=broad-except
# An exception is raised if idp name is not found.
return None
return _result["result"]
def gen_args(auth_uri, dev_auth_uri, token_uri, userinfo_uri, keys_uri,
issuer_url, client_id, secret, scope, idp_user_id, organization,
base_url):
_args = {}
if auth_uri is not None:
_args["ipaidpauthendpoint"] = auth_uri
if dev_auth_uri is not None:
_args["ipaidpdevauthendpoint"] = dev_auth_uri
if token_uri is not None:
_args["ipaidptokenendpoint"] = token_uri
if userinfo_uri is not None:
_args["ipaidpuserinfoendpoint"] = userinfo_uri
if keys_uri is not None:
_args["ipaidpkeysendpoint"] = keys_uri
if issuer_url is not None:
_args["ipaidpissuerurl"] = issuer_url
if client_id is not None:
_args["ipaidpclientid"] = client_id
if secret is not None:
_args["ipaidpclientsecret"] = secret
if scope is not None:
_args["ipaidpscope"] = scope
if idp_user_id is not None:
_args["ipaidpsub"] = idp_user_id
if organization is not None:
_args["ipaidporg"] = organization
if base_url is not None:
_args["ipaidpbaseurl"] = base_url
return _args
# Copied and adapted from FreeIPA ipaserver/plugins/idp.py
def convert_provider_to_endpoints(module, _args, provider):
"""Convert provider option to auth-uri and token-uri,.."""
if provider not in idp_providers:
module.fail_json(msg="Provider '%s' is unknown" % provider)
# For each string in the template check if a variable
# is required, it is provided as an option
points = deepcopy(idp_providers[provider])
_r = string.Template.pattern
for (_k, _v) in points.items():
# build list of variables to be replaced
subs = list(chain.from_iterable(
(filter(None, _s) for _s in _r.findall(_v))))
if subs:
for _s in subs:
if _s not in _args:
module.fail_json(msg="Parameter '%s' is missing" % _s)
points[_k] = template_str(_v, _args)
elif _k in _args:
points[_k] = _args[_k]
_args.update(points)
def main():
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# general
name=dict(type="list", elements="str", required=True,
aliases=["cn"]),
# present
auth_uri=dict(required=False, type="str", default=None,
aliases=["ipaidpauthendpoint"]),
dev_auth_uri=dict(required=False, type="str", default=None,
aliases=["ipaidpdevauthendpoint"]),
token_uri=dict(required=False, type="str", default=None,
aliases=["ipaidptokenendpoint"]),
userinfo_uri=dict(required=False, type="str", default=None,
aliases=["ipaidpuserinfoendpoint"]),
keys_uri=dict(required=False, type="str", default=None,
aliases=["ipaidpkeysendpoint"]),
issuer_url=dict(required=False, type="str", default=None,
aliases=["ipaidpissuerurl"]),
client_id=dict(required=False, type="str", default=None,
aliases=["ipaidpclientid"]),
secret=dict(required=False, type="str", default=None,
aliases=["ipaidpclientsecret"], no_log=True),
scope=dict(required=False, type="str", default=None,
aliases=["ipaidpscope"]),
idp_user_id=dict(required=False, type="str", default=None,
aliases=["ipaidpsub"]),
provider=dict(required=False, type="str", default=None,
aliases=["ipaidpprovider"],
choices=["google", "github", "microsoft", "okta",
"keycloak"]),
organization=dict(required=False, type="str", default=None,
aliases=["ipaidporg"]),
base_url=dict(required=False, type="str", default=None,
aliases=["ipaidpbaseurl"]),
rename=dict(required=False, type="str", default=None,
aliases=["new_name"]),
delete_continue=dict(required=False, type="bool", default=None,
aliases=['continue']),
# state
state=dict(type="str", default="present",
choices=["present", "absent", "renamed"]),
),
supports_check_mode=True,
# mutually_exclusive=[],
# required_one_of=[]
)
ansible_module._ansible_debug = True
# Get parameters
# general
names = ansible_module.params_get("name")
# present
auth_uri = ansible_module.params_get("auth_uri")
dev_auth_uri = ansible_module.params_get("dev_auth_uri")
token_uri = ansible_module.params_get("token_uri")
userinfo_uri = ansible_module.params_get("userinfo_uri")
keys_uri = ansible_module.params_get("keys_uri")
issuer_url = ansible_module.params_get("issuer_url")
client_id = ansible_module.params_get("client_id")
secret = ansible_module.params_get("secret")
scope = ansible_module.params_get("scope")
idp_user_id = ansible_module.params_get("idp_user_id")
provider = ansible_module.params_get("provider")
organization = ansible_module.params_get("organization")
base_url = ansible_module.params_get("base_url")
rename = ansible_module.params_get("rename")
delete_continue = ansible_module.params_get("delete_continue")
# state
state = ansible_module.params_get("state")
# Check parameters
invalid = []
if state == "present":
if len(names) != 1:
ansible_module.fail_json(
msg="Only one idp can be added at a time.")
if provider:
if any([auth_uri, dev_auth_uri, token_uri, userinfo_uri,
keys_uri]):
ansible_module.fail_json(
msg="Cannot specify both individual endpoints and IdP "
"provider")
if provider not in idp_providers:
ansible_module.fail_json(
msg="Provider '%s' is unknown" % provider)
else:
if not auth_uri:
ansible_module.fail_json(
msg="Parameter '%s' is missing" % "auth_uri")
if not dev_auth_uri:
ansible_module.fail_json(
msg="Parameter '%s' is missing" % "dev_auth_uri")
if not token_uri:
ansible_module.fail_json(
msg="Parameter '%s' is missing" % "token_uri")
if not userinfo_uri:
ansible_module.fail_json(
msg="Parameter '%s' is missing" % "userinfo_uri")
invalid = ["rename", "delete_continue"]
else:
# state renamed and absent
invalid = ["auth_uri", "dev_auth_uri", "token_uri", "userinfo_uri",
"keys_uri", "issuer_url", "client_id", "secret", "scope",
"idp_user_id", "provider", "organization", "base_url"]
if state == "renamed":
if len(names) != 1:
ansible_module.fail_json(
msg="Only one permission can be renamed at a time.")
invalid += ["delete_continue"]
if state == "absent":
if len(names) < 1:
ansible_module.fail_json(msg="No name given.")
invalid += ["rename"]
ansible_module.params_fail_used_invalid(invalid, state)
# Init
changed = False
exit_args = {}
# Connect to IPA API
with ansible_module.ipa_connect():
if not ansible_module.ipa_command_exists("idp_add"):
ansible_module.fail_json(
msg="Managing idp is not supported by your IPA version")
commands = []
for name in names:
# Make sure idp exists
res_find = find_idp(ansible_module, name)
# Create command
if state == "present":
# Generate args
args = gen_args(auth_uri, dev_auth_uri, token_uri,
userinfo_uri, keys_uri, issuer_url, client_id,
secret, scope, idp_user_id, organization,
base_url)
if provider is not None:
convert_provider_to_endpoints(ansible_module, args,
provider)
# Found the idp
if res_find is not None:
# The parameters ipaidpprovider, ipaidporg and
# ipaidpbaseurl are only available for idp-add to create
# then endpoints using provider, Therefore we have to
# remove them from args.
for arg in ["ipaidpprovider", "ipaidporg",
"ipaidpbaseurl"]:
if arg in args:
del args[arg]
# For all settings is args, check if there are
# different settings in the find result.
# If yes: modify
if not compare_args_ipa(ansible_module, args,
res_find):
commands.append([name, "idp_mod", args])
else:
commands.append([name, "idp_add", args])
elif state == "absent":
if res_find is not None:
_args = {}
if delete_continue is not None:
_args = {"continue": delete_continue}
commands.append([name, "idp_del", _args])
elif state == "renamed":
if not rename:
ansible_module.fail_json(msg="No rename value given.")
if res_find is None:
ansible_module.fail_json(
msg="No idp found to be renamed: '%s'" % (name))
if name != rename:
commands.append(
[name, "idp_mod", {"rename": rename}])
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Execute commands
changed = ansible_module.execute_ipa_commands(commands)
# Done
ansible_module.exit_json(changed=changed, **exit_args)
if __name__ == "__main__":
main()

View File

@@ -157,8 +157,7 @@ def find_idrange(module, name):
except Exception: # pylint: disable=broad-except
# An exception is raised if idrange name is not found.
return None
else:
return _result["result"]
return _result["result"]
def gen_args(

View File

@@ -0,0 +1,362 @@
# -*- coding: utf-8 -*-
# Authors:
# Thomas Woerner <twoerner@redhat.com>
#
# Copyright (C) 2023 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: ipaidview
short_description: Manage FreeIPA idview
description: Manage FreeIPA idview and idview host members
extends_documentation_fragment:
- ipamodule_base_docs
options:
name:
description: The list of idview name strings.
required: true
type: list
elements: str
aliases: ["cn"]
description:
description: Description
required: False
type: str
aliases: ["desc"]
domain_resolution_order:
description: |
Colon-separated list of domains used for short name qualification
required: False
type: str
aliases: ["ipadomainresolutionorder"]
host:
description: Hosts to apply the ID View to
required: False
type: list
elements: str
aliases: ["hosts"]
rename:
description: Rename the ID view object
required: False
type: str
aliases: ["new_name"]
delete_continue:
description: |
Continuous mode. Don't stop on errors.
Valid only if `state` is `absent`.
required: false
type: bool
aliases: ["continue"]
action:
description: Work on idview or member level.
choices: ["idview", "member"]
default: idview
type: str
state:
description: The state to ensure.
choices: ["present", "absent", "renamed"]
default: present
type: str
author:
- Thomas Woerner (@t-woerner)
"""
EXAMPLES = """
# Ensure idview test_idview is present
- ipaidview:
ipaadmin_password: SomeADMINpassword
name: test_idview
# name: Ensure host testhost.example.com is applied to idview test_idview
- ipaidview:
ipaadmin_password: SomeADMINpassword
name: test_idview
host: testhost.example.com
action: member
# Ensure host testhost.example.com is not applied to idview test_idview
- ipaidview:
ipaadmin_password: SomeADMINpassword
name: test_idview
host: testhost.example.com
action: member
state: absent
# Ensure idview "test_idview" is present with domain_resolution_order for
# "ad.example.com:ipa.example.com"
- ipaidview:
ipaadmin_password: SomeADMINpassword
name: test_idview
domain_resolution_order: "ad.example.com:ipa.example.com"
# Ensure idview test_idview is absent
- ipaidview:
ipaadmin_password: SomeADMINpassword
name: test_idview
state: absent
"""
RETURN = """
"""
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, gen_add_list, \
gen_intersection_list, ipalib_errors
from ansible.module_utils import six
if six.PY3:
unicode = str
def find_idview(module, name):
"""Find if a idview with the given name already exist."""
try:
_result = module.ipa_command("idview_show", name, {"all": True})
except Exception: # pylint: disable=broad-except
# An exception is raised if idview name is not found.
return None
return _result["result"]
def valid_host(module, name):
try:
module.ipa_command("host_show", name, {})
except ipalib_errors.NotFound:
return False
return True
def gen_args(description, domain_resolution_order):
_args = {}
if description is not None:
_args["description"] = description
if domain_resolution_order is not None:
_args["ipadomainresolutionorder"] = domain_resolution_order
return _args
def gen_member_args(host):
_args = {}
if host is not None:
_args["host"] = host
return _args
def main():
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# general
name=dict(type="list", elements="str", required=True,
aliases=["cn"]),
# present
description=dict(type="str", required=False, aliases=["desc"]),
domain_resolution_order=dict(type="str", required=False,
aliases=["ipadomainresolutionorder"]),
host=dict(type="list", elements="str", required=False,
aliases=["hosts"], default=None),
rename=dict(type="str", required=False, aliases=["new_name"]),
delete_continue=dict(type="bool", required=False,
aliases=['continue'], default=None),
# action
action=dict(type="str", default="idview",
choices=["member", "idview"]),
# state
state=dict(type="str", default="present",
choices=["present", "absent", "renamed"]),
),
supports_check_mode=True,
)
ansible_module._ansible_debug = True
# Get parameters
# general
names = ansible_module.params_get("name")
# present
description = ansible_module.params_get("description")
domain_resolution_order = ansible_module.params_get(
"domain_resolution_order")
host = ansible_module.params_get("host")
rename = ansible_module.params_get("rename")
action = ansible_module.params_get("action")
# absent
delete_continue = ansible_module.params_get("delete_continue")
# state
state = ansible_module.params_get("state")
# Check parameters
invalid = []
if state == "present":
if len(names) != 1:
ansible_module.fail_json(
msg="Only one idview can be added at a time.")
invalid = ["delete_continue", "rename"]
if action == "member":
invalid += ["description", "domain_resolution_order"]
if state == "renamed":
if len(names) != 1:
ansible_module.fail_json(
msg="Only one idoverridegroup can be renamed at a time.")
if not rename:
ansible_module.fail_json(
msg="Rename is required for state: renamed.")
if action == "member":
ansible_module.fail_json(
msg="Action member can not be used with state: renamed.")
invalid = ["description", "domain_resolution_order", "host",
"delete_continue"]
if state == "absent":
if len(names) < 1:
ansible_module.fail_json(msg="No name given.")
invalid = ["description", "domain_resolution_order", "rename"]
if action == "idview":
invalid += ["host"]
ansible_module.params_fail_used_invalid(invalid, state, action)
# Init
changed = False
exit_args = {}
# Connect to IPA API
with ansible_module.ipa_connect():
commands = []
for name in names:
# Make sure idview exists
res_find = find_idview(ansible_module, name)
# add/del lists
host_add, host_del = [], []
# Create command
if state == "present":
# Generate args
args = gen_args(description, domain_resolution_order)
if action == "idview":
# Found the idview
if res_find is not None:
# For all settings is args, check if there are
# different settings in the find result.
# If yes: modify
if not compare_args_ipa(ansible_module, args,
res_find):
commands.append([name, "idview_mod", args])
else:
commands.append([name, "idview_add", args])
res_find = {}
member_args = gen_member_args(host)
if not compare_args_ipa(ansible_module, member_args,
res_find):
# Generate addition and removal lists
host_add, host_del = gen_add_del_lists(
host, res_find.get("appliedtohosts"))
elif action == "member":
if res_find is None:
ansible_module.fail_json(
msg="No idview '%s'" % name)
# Reduce add lists for host
# to new entries only that are not in res_find.
if host is not None:
host_add = gen_add_list(
host, res_find.get("appliedtohosts"))
elif state == "absent":
if action == "idview":
if res_find is not None:
commands.append(
[name, "idview_del",
{"continue": delete_continue or False}]
)
elif action == "member":
if res_find is None:
ansible_module.fail_json(
msg="No idview '%s'" % name)
# Reduce del lists of member_host
# to the entries only that are in res_find.
if host is not None:
host_del = gen_intersection_list(
host, res_find.get("appliedtohosts"))
elif state == "renamed":
if res_find is None:
ansible_module.fail_json(msg="No idview '%s'" % name)
else:
commands.append([name, 'idview_mod', {"rename": rename}])
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Member management
# Add members
if host_add:
for host in host_add:
if not valid_host(ansible_module, host):
ansible_module.fail_json("Invalid host '%s'" % host)
commands.append([name, "idview_apply", {"host": host_add}])
# Remove members
if host_del:
# idview_unapply does not have the idview name (cn) as an arg.
# It is removing the host from any idview it is applied to.
# But as we create the intersection with the list of hosts of
# the idview, we emulate the correct behaviour. But this means
# that there is no general idview_unapply like in the cli.
commands.append([None, "idview_unapply", {"host": host_del}])
# Execute commands
changed = ansible_module.execute_ipa_commands(commands)
# Done
ansible_module.exit_json(changed=changed, **exit_args)
if __name__ == "__main__":
main()

View File

@@ -86,8 +86,7 @@ def find_location(module, name):
except Exception: # pylint: disable=broad-except
# An exception is raised if location name is not found.
return None
else:
return _result["result"]
return _result["result"]
def gen_args(description):

View File

@@ -164,8 +164,7 @@ def find_permission(module, name):
except Exception: # pylint: disable=broad-except
# An exception is raised if permission name is not found.
return None
else:
return _result["result"]
return _result["result"]
def gen_args(right, attrs, bindtype, subtree,

View File

@@ -138,8 +138,7 @@ def find_privilege(module, name):
except Exception: # pylint: disable=broad-except
# An exception is raised if privilege name is not found.
return None
else:
return _result["result"]
return _result["result"]
def main():

View File

@@ -45,82 +45,84 @@ options:
required: false
aliases: ["cn"]
maxlife:
description: Maximum password lifetime (in days)
description: Maximum password lifetime (in days). (int or "")
type: str
required: false
aliases: ["krbmaxpwdlife"]
minlife:
description: Minimum password lifetime (in hours)
description: Minimum password lifetime (in hours). (int or "")
type: str
required: false
aliases: ["krbminpwdlife"]
history:
description: Password history size
description: Password history size. (int or "")
type: str
required: false
aliases: ["krbpwdhistorylength"]
minclasses:
description: Minimum number of character classes
description: Minimum number of character classes. (int or "")
type: str
required: false
aliases: ["krbpwdmindiffchars"]
minlength:
description: Minimum length of password
description: Minimum length of password. (int or "")
type: str
required: false
aliases: ["krbpwdminlength"]
priority:
description: Priority of the policy (higher number means lower priority)
description: >
Priority of the policy (higher number means lower priority). (int or "")
type: str
required: false
aliases: ["cospriority"]
maxfail:
description: Consecutive failures before lockout
description: Consecutive failures before lockout. (int or "")
type: str
required: false
aliases: ["krbpwdmaxfailure"]
failinterval:
description: Period after which failure count will be reset (seconds)
description: >
Period after which failure count will be reset (seconds). (int or "")
type: str
required: false
aliases: ["krbpwdfailurecountinterval"]
lockouttime:
description: Period for which lockout is enforced (seconds)
description: Period for which lockout is enforced (seconds). (int or "")
type: str
required: false
aliases: ["krbpwdlockoutduration"]
maxrepeat:
description: >
Maximum number of same consecutive characters.
Requires IPA 4.9+
Requires IPA 4.9+. (int or "")
type: str
required: false
aliases: ["ipapwdmaxrepeat"]
maxsequence:
description: >
The maximum length of monotonic character sequences (abcd).
Requires IPA 4.9+
Requires IPA 4.9+. (int or "")
type: str
required: false
aliases: ["ipapwdmaxsequence"]
dictcheck:
description: >
Check if the password is a dictionary word.
Requires IPA 4.9+
Requires IPA 4.9+. (bool or "")
type: str
required: false
aliases: ["ipapwdictcheck"]
usercheck:
description: >
Check if the password contains the username.
Requires IPA 4.9+
Requires IPA 4.9+. (bool or "")
type: str
required: false
aliases: ["ipapwdusercheck"]
gracelimit:
description: >
Number of LDAP authentications allowed after expiration.
Requires IPA 4.10.1+
Requires IPA 4.10.1+. (int or "")
type: str
required: false
aliases: ["passwordgracelimit"]
@@ -151,7 +153,7 @@ RETURN = """
"""
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa
IPAAnsibleModule, compare_args_ipa, boolean
def find_pwpolicy(module, name):
@@ -359,17 +361,12 @@ def main():
gracelimit = int_or_empty_param(gracelimit, "gracelimit")
def bool_or_empty_param(value, param): # pylint: disable=R1710
# As of Ansible 2.14, values True, False, Yes an No, with variable
# capitalization are accepted by Ansible.
if not value:
if value is None or value == "":
return value
if value in ["TRUE", "True", "true", "YES", "Yes", "yes"]:
return True
if value in ["FALSE", "False", "false", "NO", "No", "no"]:
return False
ansible_module.fail_json(
msg="Invalid value '%s' for argument '%s'." % (value, param)
)
try:
return boolean(value)
except TypeError as terr:
ansible_module.fail_json(msg="Param '%s': %s" % (param, str(terr)))
dictcheck = bool_or_empty_param(dictcheck, "dictcheck")
usercheck = bool_or_empty_param(usercheck, "usercheck")

View File

@@ -143,8 +143,7 @@ def find_role(module, name):
except Exception: # pylint: disable=broad-except
# An exception is raised if role name is not found.
return None
else:
return _result["result"]
return _result["result"]
def gen_args(module):

View File

@@ -123,8 +123,7 @@ def find_selfservice(module, name):
except Exception: # pylint: disable=broad-except
# An exception is raised if selfservice name is not found.
return None
else:
return _result["result"]
return _result["result"]
def gen_args(permission, attribute):

View File

@@ -45,9 +45,9 @@ options:
aliases: ["cn"]
location:
description: |
The server location string.
"" for location reset.
Only in state: present.
The server DNS location.
Only available with 'state: present'.
Use "" for location reset.
type: str
required: false
aliases: ["ipalocation_location"]
@@ -55,46 +55,46 @@ options:
description: |
Weight for server services
Values 0 to 65535, -1 for weight reset.
Only in state: present.
Only available with 'state: present'.
required: false
type: int
aliases: ["ipaserviceweight"]
hidden:
description: |
Set hidden state of a server.
Only in state: present.
Only available with 'state: present'.
required: false
type: bool
no_members:
description: |
Suppress processing of membership attributes
Only in state: present.
Only available with 'state: present'.
required: false
type: bool
delete_continue:
description: |
Continuous mode: Don't stop on errors.
Only in state: absent.
Only available with 'state: absent'.
required: false
type: bool
aliases: ["continue"]
ignore_last_of_role:
description: |
Skip a check whether the last CA master or DNS server is removed.
Only in state: absent.
Only available with 'state: absent'.
required: false
type: bool
ignore_topology_disconnect:
description: |
Ignore topology connectivity problems after removal.
Only in state: absent.
Only available with 'state: absent'.
required: false
type: bool
force:
description: |
Force server removal even if it does not exist.
Will always result in changed.
Only in state: absent.
Only available with 'state: absent'.
required: false
type: bool
state:
@@ -202,8 +202,7 @@ def find_server(module, name):
except Exception: # pylint: disable=broad-except
# An exception is raised if server name is not found.
return None
else:
return _result["result"]
return _result["result"]
def server_role_status(module, name):
@@ -218,8 +217,7 @@ def server_role_status(module, name):
except Exception: # pylint: disable=broad-except
# An exception is raised if server name is not found.
return None
else:
return _result["result"][0]
return _result["result"][0]
def gen_args(location, service_weight, no_members, delete_continue,

View File

@@ -74,7 +74,7 @@ options:
type: list
elements: str
required: false
choices: ["otp", "radius", "pkinit", "hardened", ""]
choices: ["otp", "radius", "pkinit", "hardened", "idp", ""]
aliases: ["krbprincipalauthind"]
skip_host_check:
description: Skip checking if host object exists.
@@ -185,7 +185,7 @@ options:
type: list
elements: str
required: false
choices: ["otp", "radius", "pkinit", "hardened", ""]
choices: ["otp", "radius", "pkinit", "hardened", "idp", ""]
aliases: ["krbprincipalauthind"]
skip_host_check:
description: Skip checking if host object exists.
@@ -414,15 +414,15 @@ def gen_args(pac_type, auth_ind, skip_host_check, force, requires_pre_auth,
if auth_ind is not None:
_args['krbprincipalauthind'] = auth_ind
if skip_host_check is not None:
_args['skip_host_check'] = (skip_host_check)
_args['skip_host_check'] = skip_host_check
if force is not None:
_args['force'] = (force)
_args['force'] = force
if requires_pre_auth is not None:
_args['ipakrbrequirespreauth'] = (requires_pre_auth)
_args['ipakrbrequirespreauth'] = requires_pre_auth
if ok_as_delegate is not None:
_args['ipakrbokasdelegate'] = (ok_as_delegate)
_args['ipakrbokasdelegate'] = ok_as_delegate
if ok_to_auth_as_delegate is not None:
_args['ipakrboktoauthasdelegate'] = (ok_to_auth_as_delegate)
_args['ipakrboktoauthasdelegate'] = ok_to_auth_as_delegate
return _args
@@ -433,9 +433,9 @@ def gen_args_smb(netbiosname, ok_as_delegate, ok_to_auth_as_delegate):
if netbiosname is not None:
_args['ipantflatname'] = netbiosname
if ok_as_delegate is not None:
_args['ipakrbokasdelegate'] = (ok_as_delegate)
_args['ipakrbokasdelegate'] = ok_as_delegate
if ok_to_auth_as_delegate is not None:
_args['ipakrboktoauthasdelegate'] = (ok_to_auth_as_delegate)
_args['ipakrboktoauthasdelegate'] = ok_to_auth_as_delegate
return _args
@@ -491,6 +491,15 @@ def check_parameters(module, state, action, names):
module.params_fail_used_invalid(invalid, state, action)
def check_authind(module, auth_ind):
_invalid = module.ipa_command_invalid_param_choices(
"service_add", "krbprincipalauthind", auth_ind)
if _invalid:
module.fail_json(
msg="The use of krbprincipalauthind '%s' is not supported "
"by your IPA version" % "','".join(_invalid))
def init_ansible_module():
service_spec = dict(
# service attributesstr
@@ -506,7 +515,8 @@ def init_ansible_module():
choices=["MS-PAC", "PAD", "NONE", ""]),
auth_ind=dict(type="list", elements="str",
aliases=["krbprincipalauthind"],
choices=["otp", "radius", "pkinit", "hardened", ""]),
choices=["otp", "radius", "pkinit", "hardened", "idp",
""]),
skip_host_check=dict(type="bool"),
force=dict(type="bool"),
requires_pre_auth=dict(
@@ -642,6 +652,7 @@ def main():
if skip_host_check and not has_skip_host_check:
ansible_module.fail_json(
msg="Skipping host check is not supported by your IPA version")
check_authind(ansible_module, auth_ind)
commands = []
keytab_members = ["user", "group", "host", "hostgroup"]
@@ -664,6 +675,7 @@ def main():
certificate = [cert.strip() for cert in certificate]
pac_type = service.get("pac_type")
auth_ind = service.get("auth_ind")
check_authind(ansible_module, auth_ind)
skip_host_check = service.get("skip_host_check")
if skip_host_check and not has_skip_host_check:
ansible_module.fail_json(

View File

@@ -145,8 +145,7 @@ def find_servicedelegationrule(module, name):
except Exception: # pylint: disable=broad-except
# An exception is raised if servicedelegationrule name is not found.
return None
else:
return _result["result"]
return _result["result"]
def check_targets(module, targets):

View File

@@ -121,8 +121,7 @@ def find_servicedelegationtarget(module, name):
except Exception: # pylint: disable=broad-except
# An exception is raised if servicedelegationtarget name is not found.
return None
else:
return _result["result"]
return _result["result"]
def main():

View File

@@ -122,8 +122,7 @@ def find_sudocmdgroup(module, name):
_result = module.ipa_command("sudocmdgroup_show", name, args)
except ipalib_errors.NotFound:
return None
else:
return _result["result"]
return _result["result"]
def gen_args(description, nomembers):

View File

@@ -80,6 +80,10 @@ options:
description: The home directory
type: str
required: false
gecos:
description: The GECOS
type: str
required: false
shell:
description: The login shell
type: str
@@ -133,6 +137,10 @@ options:
type: int
required: false
aliases: ["gidnumber"]
street:
description: Street address
type: str
required: false
city:
description: City
type: str
@@ -200,7 +208,7 @@ options:
Use empty string to reset userauthtype to the initial value.
type: list
elements: str
choices: ['password', 'radius', 'otp', '']
choices: ["password", "radius", "otp", "pkinit", "hardened", "idp", ""]
required: false
aliases: ["ipauserauthtype"]
userclass:
@@ -234,10 +242,45 @@ options:
description: Employee Type
type: str
required: false
smb_logon_script:
description: SMB logon script path
type: str
required: false
aliases: ["ipantlogonscript"]
smb_profile_path:
description: SMB profile path
type: str
required: false
aliases: ["ipantprofilepath"]
smb_home_dir:
description: SMB Home Directory
type: str
required: false
aliases: ["ipanthomedirectory"]
smb_home_drive:
description: SMB Home Directory Drive
type: str
required: false
choices: [
'A:', 'B:', 'C:', 'D:', 'E:', 'F:', 'G:', 'H:', 'I:', 'J:',
'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:', 'S:', 'T:',
'U:', 'V:', 'W:', 'X:', 'Y:', 'Z:', ''
]
aliases: ["ipanthomedirectorydrive"]
preferredlanguage:
description: Preferred Language
type: str
required: false
idp:
description: External IdP configuration
type: str
required: false
aliases: ["ipaidpconfiglink"]
idp_user_id:
description: A string that identifies the user at external IdP
type: str
required: false
aliases: ["ipaidpsub"]
certificate:
description: List of base-64 encoded user certificates
type: list
@@ -304,6 +347,10 @@ options:
description: The home directory
type: str
required: false
gecos:
description: The GECOS
type: str
required: false
shell:
description: The login shell
type: str
@@ -357,6 +404,10 @@ options:
type: int
required: false
aliases: ["gidnumber"]
street:
description: Street address
type: str
required: false
city:
description: City
type: str
@@ -424,7 +475,7 @@ options:
Use empty string to reset userauthtype to the initial value.
type: list
elements: str
choices: ['password', 'radius', 'otp', '']
choices: ["password", "radius", "otp", "pkinit", "hardened", "idp", ""]
required: false
aliases: ["ipauserauthtype"]
userclass:
@@ -458,10 +509,45 @@ options:
description: Employee Type
type: str
required: false
smb_logon_script:
description: SMB logon script path
type: str
required: false
aliases: ["ipantlogonscript"]
smb_profile_path:
description: SMB profile path
type: str
required: false
aliases: ["ipantprofilepath"]
smb_home_dir:
description: SMB Home Directory
type: str
required: false
aliases: ["ipanthomedirectory"]
smb_home_drive:
description: SMB Home Directory Drive
type: str
required: false
choices: [
'A:', 'B:', 'C:', 'D:', 'E:', 'F:', 'G:', 'H:', 'I:', 'J:',
'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:', 'S:', 'T:',
'U:', 'V:', 'W:', 'X:', 'Y:', 'Z:', ''
]
aliases: ["ipanthomedirectorydrive"]
preferredlanguage:
description: Preferred Language
type: str
required: false
idp:
description: External IdP configuration
type: str
required: false
aliases: ["ipaidpconfiglink"]
idp_user_id:
description: A string that identifies the user at external IdP
type: str
required: false
aliases: ["ipaidpsub"]
certificate:
description: List of base-64 encoded user certificates
type: list
@@ -597,6 +683,17 @@ EXAMPLES = """
ipaadmin_password: SomeADMINpassword
name: pinky,brain
state: disabled
# Ensure a user has SMB attributes
- ipauser:
ipaadmin_password: SomeADMINpassword
name: smbuser
first: SMB
last: User
smb_logon_script: N:\\logonscripts\\startup
smb_profile_path: \\\\server\\profiles\\some_profile
smb_home_dir: \\\\users\\home\\smbuser
smb_home_drive: "U:"
"""
RETURN = """
@@ -652,12 +749,14 @@ def find_user(module, name):
return _result
def gen_args(first, last, fullname, displayname, initials, homedir, shell,
email, principalexpiration, passwordexpiration, password,
random, uid, gid, city, userstate, postalcode, phone, mobile,
pager, fax, orgunit, title, carlicense, sshpubkey, userauthtype,
userclass, radius, radiususer, departmentnumber, employeenumber,
employeetype, preferredlanguage, noprivate, nomembers):
def gen_args(first, last, fullname, displayname, initials, homedir, gecos,
shell, email, principalexpiration, passwordexpiration, password,
random, uid, gid, street, city, userstate, postalcode, phone,
mobile, pager, fax, orgunit, title, carlicense, sshpubkey,
userauthtype, userclass, radius, radiususer, departmentnumber,
employeenumber, employeetype, preferredlanguage, smb_logon_script,
smb_profile_path, smb_home_dir, smb_home_drive, idp, idp_user_id,
noprivate, nomembers):
# principal, manager, certificate and certmapdata are handled not in here
_args = {}
if first is not None:
@@ -672,6 +771,8 @@ def gen_args(first, last, fullname, displayname, initials, homedir, shell,
_args["initials"] = initials
if homedir is not None:
_args["homedirectory"] = homedir
if gecos is not None:
_args["gecos"] = gecos
if shell is not None:
_args["loginshell"] = shell
if email is not None and len(email) > 0:
@@ -688,6 +789,8 @@ def gen_args(first, last, fullname, displayname, initials, homedir, shell,
_args["uidnumber"] = to_text(str(uid))
if gid is not None:
_args["gidnumber"] = to_text(str(gid))
if street is not None:
_args["street"] = street
if city is not None:
_args["l"] = city
if userstate is not None:
@@ -726,48 +829,57 @@ def gen_args(first, last, fullname, displayname, initials, homedir, shell,
_args["employeetype"] = employeetype
if preferredlanguage is not None:
_args["preferredlanguage"] = preferredlanguage
if idp is not None:
_args["ipaidpconfiglink"] = idp
if idp_user_id is not None:
_args["ipaidpsub"] = idp_user_id
if noprivate is not None:
_args["noprivate"] = noprivate
if nomembers is not None:
_args["no_members"] = nomembers
if smb_logon_script is not None:
_args["ipantlogonscript"] = smb_logon_script
if smb_profile_path is not None:
_args["ipantprofilepath"] = smb_profile_path
if smb_home_dir is not None:
_args["ipanthomedirectory"] = smb_home_dir
if smb_home_drive is not None:
_args["ipanthomedirectorydrive"] = smb_home_drive
return _args
def check_parameters( # pylint: disable=unused-argument
module, state, action, first, last, fullname, displayname, initials,
homedir, shell, email, principal, principalexpiration,
passwordexpiration, password, random, uid, gid, city, phone, mobile,
pager, fax, orgunit, title, manager, carlicense, sshpubkey,
homedir, gecos, shell, email, principal, principalexpiration,
passwordexpiration, password, random, uid, gid, street, city, phone,
mobile, pager, fax, orgunit, title, manager, carlicense, sshpubkey,
userauthtype, userclass, radius, radiususer, departmentnumber,
employeenumber, employeetype, preferredlanguage, certificate,
certmapdata, noprivate, nomembers, preserve, update_password):
invalid = []
if state == "present":
if action == "member":
invalid = ["first", "last", "fullname", "displayname", "initials",
"homedir", "shell", "email", "principalexpiration",
"passwordexpiration", "password", "random", "uid",
"gid", "city", "phone", "mobile", "pager", "fax",
"orgunit", "title", "carlicense", "sshpubkey",
"userauthtype", "userclass", "radius", "radiususer",
"departmentnumber", "employeenumber", "employeetype",
"preferredlanguage", "noprivate", "nomembers",
"preserve", "update_password"]
certmapdata, noprivate, nomembers, preserve, update_password,
smb_logon_script, smb_profile_path, smb_home_dir, smb_home_drive,
idp, ipa_user_id,
):
if state == "present" and action == "user":
invalid = ["preserve"]
else:
invalid = ["first", "last", "fullname", "displayname", "initials",
"homedir", "shell", "email", "principalexpiration",
"passwordexpiration", "password", "random", "uid",
"gid", "city", "phone", "mobile", "pager", "fax",
"orgunit", "title", "carlicense", "sshpubkey",
"userauthtype", "userclass", "radius", "radiususer",
"departmentnumber", "employeenumber", "employeetype",
"preferredlanguage", "noprivate", "nomembers",
"update_password"]
if action == "user":
invalid.extend(["principal", "manager",
"certificate", "certmapdata",
])
invalid = [
"first", "last", "fullname", "displayname", "initials", "homedir",
"shell", "email", "principalexpiration", "passwordexpiration",
"password", "random", "uid", "gid", "street", "city", "phone",
"mobile", "pager", "fax", "orgunit", "title", "carlicense",
"sshpubkey", "userauthtype", "userclass", "radius", "radiususer",
"departmentnumber", "employeenumber", "employeetype",
"preferredlanguage", "noprivate", "nomembers", "update_password",
"gecos", "smb_logon_script", "smb_profile_path", "smb_home_dir",
"smb_home_drive", "idp", "idp_user_id"
]
if state == "present" and action == "member":
invalid.append("preserve")
else:
if action == "user":
invalid.extend(
["principal", "manager", "certificate", "certmapdata"])
if state != "absent" and preserve is not None:
module.fail_json(
@@ -801,6 +913,15 @@ def check_parameters( # pylint: disable=unused-argument
module.fail_json(msg="certmapdata: subject is missing")
def check_userauthtype(module, userauthtype):
_invalid = module.ipa_command_invalid_param_choices(
"user_add", "ipauserauthtype", userauthtype)
if _invalid:
module.fail_json(
msg="The use of userauthtype '%s' is not supported "
"by your IPA version" % "','".join(_invalid))
def extend_emails(email, default_email_domain):
if email is not None:
return ["%s@%s" % (_email, default_email_domain)
@@ -902,6 +1023,7 @@ def main():
displayname=dict(type="str", default=None),
initials=dict(type="str", default=None),
homedir=dict(type="str", default=None),
gecos=dict(type="str", default=None),
shell=dict(type="str", aliases=["loginshell"], default=None),
email=dict(type="list", elements="str", default=None),
principal=dict(type="list", elements="str",
@@ -917,6 +1039,7 @@ def main():
random=dict(type='bool', default=None),
uid=dict(type="int", aliases=["uidnumber"], default=None),
gid=dict(type="int", aliases=["gidnumber"], default=None),
street=dict(type="str", default=None),
city=dict(type="str", default=None),
userstate=dict(type="str", aliases=["st"], default=None),
postalcode=dict(type="str", aliases=["zip"], default=None),
@@ -934,7 +1057,8 @@ def main():
default=None),
userauthtype=dict(type='list', elements="str",
aliases=["ipauserauthtype"], default=None,
choices=['password', 'radius', 'otp', '']),
choices=["password", "radius", "otp", "pkinit",
"hardened", "idp", ""]),
userclass=dict(type="list", elements="str", aliases=["class"],
default=None),
radius=dict(type="str", aliases=["ipatokenradiusconfiglink"],
@@ -945,6 +1069,17 @@ def main():
departmentnumber=dict(type="list", elements="str", default=None),
employeenumber=dict(type="str", default=None),
employeetype=dict(type="str", default=None),
smb_logon_script=dict(type="str", default=None,
aliases=["ipantlogonscript"]),
smb_profile_path=dict(type="str", default=None,
aliases=["ipantprofilepath"]),
smb_home_dir=dict(type="str", default=None,
aliases=["ipanthomedirectory"]),
smb_home_drive=dict(type="str", default=None,
choices=[
("%c:" % chr(x))
for x in range(ord('A'), ord('Z') + 1)
] + [""], aliases=["ipanthomedirectorydrive"]),
preferredlanguage=dict(type="str", default=None),
certificate=dict(type="list", elements="str",
aliases=["usercertificate"], default=None),
@@ -959,6 +1094,9 @@ def main():
elements='dict', required=False),
noprivate=dict(type='bool', default=None),
nomembers=dict(type='bool', default=None),
idp=dict(type="str", default=None, aliases=['ipaidpconfiglink']),
idp_user_id=dict(type="str", default=None,
aliases=['ipaidpconfiglink']),
)
ansible_module = IPAAnsibleModule(
@@ -1015,6 +1153,7 @@ def main():
displayname = ansible_module.params_get("displayname")
initials = ansible_module.params_get("initials")
homedir = ansible_module.params_get("homedir")
gecos = ansible_module.params_get("gecos")
shell = ansible_module.params_get("shell")
email = ansible_module.params_get("email")
principal = ansible_module.params_get("principal")
@@ -1033,6 +1172,7 @@ def main():
random = ansible_module.params_get("random")
uid = ansible_module.params_get("uid")
gid = ansible_module.params_get("gid")
street = ansible_module.params_get("street")
city = ansible_module.params_get("city")
userstate = ansible_module.params_get("userstate")
postalcode = ansible_module.params_get("postalcode")
@@ -1055,6 +1195,12 @@ def main():
employeenumber = ansible_module.params_get("employeenumber")
employeetype = ansible_module.params_get("employeetype")
preferredlanguage = ansible_module.params_get("preferredlanguage")
smb_logon_script = ansible_module.params_get("smb_logon_script")
smb_profile_path = ansible_module.params_get("smb_profile_path")
smb_home_dir = ansible_module.params_get("smb_home_dir")
smb_home_drive = ansible_module.params_get("smb_home_drive")
idp = ansible_module.params_get("idp")
idp_user_id = ansible_module.params_get("idp_user_id")
certificate = ansible_module.params_get("certificate")
certmapdata = ansible_module.params_get("certmapdata")
noprivate = ansible_module.params_get("noprivate")
@@ -1080,13 +1226,15 @@ def main():
check_parameters(
ansible_module, state, action,
first, last, fullname, displayname, initials, homedir, shell, email,
first, last, fullname, displayname, initials, homedir, gecos, shell,
email,
principal, principalexpiration, passwordexpiration, password, random,
uid, gid, city, phone, mobile, pager, fax, orgunit, title, manager,
carlicense, sshpubkey, userauthtype, userclass, radius, radiususer,
departmentnumber, employeenumber, employeetype, preferredlanguage,
certificate, certmapdata, noprivate, nomembers, preserve,
update_password)
uid, gid, street, city, phone, mobile, pager, fax, orgunit, title,
manager, carlicense, sshpubkey, userauthtype, userclass, radius,
radiususer, departmentnumber, employeenumber, employeetype,
preferredlanguage, certificate, certmapdata, noprivate, nomembers,
preserve, update_password, smb_logon_script, smb_profile_path,
smb_home_dir, smb_home_drive, idp, idp_user_id)
certmapdata = convert_certmapdata(certmapdata)
# Use users if names is None
@@ -1105,6 +1253,10 @@ def main():
server_realm = ansible_module.ipa_get_realm()
# Check API specific parameters
check_userauthtype(ansible_module, userauthtype)
# Default email domain
result = ansible_module.ipa_command_no_name("config_show", {})
@@ -1133,6 +1285,7 @@ def main():
displayname = user.get("displayname")
initials = user.get("initials")
homedir = user.get("homedir")
gecos = user.get("gecos")
shell = user.get("shell")
email = user.get("email")
principal = user.get("principal")
@@ -1150,6 +1303,7 @@ def main():
random = user.get("random")
uid = user.get("uid")
gid = user.get("gid")
street = user.get("street")
city = user.get("city")
userstate = user.get("userstate")
postalcode = user.get("postalcode")
@@ -1170,6 +1324,12 @@ def main():
employeenumber = user.get("employeenumber")
employeetype = user.get("employeetype")
preferredlanguage = user.get("preferredlanguage")
smb_logon_script = user.get("smb_logon_script")
smb_profile_path = user.get("smb_profile_path")
smb_home_dir = user.get("smb_home_dir")
smb_home_drive = user.get("smb_home_drive")
idp = user.get("idp")
idp_user_id = user.get("idp_user_id")
certificate = user.get("certificate")
certmapdata = user.get("certmapdata")
noprivate = user.get("noprivate")
@@ -1178,16 +1338,21 @@ def main():
check_parameters(
ansible_module, state, action,
first, last, fullname, displayname, initials, homedir,
shell, email, principal, principalexpiration,
passwordexpiration, password, random, uid, gid, city,
phone, mobile, pager, fax, orgunit, title, manager,
gecos, shell, email, principal, principalexpiration,
passwordexpiration, password, random, uid, gid, street,
city, phone, mobile, pager, fax, orgunit, title, manager,
carlicense, sshpubkey, userauthtype, userclass, radius,
radiususer, departmentnumber, employeenumber,
employeetype, preferredlanguage, certificate,
certmapdata, noprivate, nomembers, preserve,
update_password)
update_password, smb_logon_script, smb_profile_path,
smb_home_dir, smb_home_drive, idp, idp_user_id)
certmapdata = convert_certmapdata(certmapdata)
# Check API specific parameters
check_userauthtype(ansible_module, userauthtype)
# Extend email addresses
email = extend_emails(email, default_email_domain)
@@ -1227,6 +1392,34 @@ def main():
msg="The use of certmapdata is not supported by "
"your IPA version")
# Check if SMB attributes are available
if (
any([
smb_logon_script, smb_profile_path, smb_home_dir,
smb_home_drive
])
and not ansible_module.ipa_command_param_exists(
"user_mod", "ipanthomedirectory"
)
):
ansible_module.fail_json(
msg="The use of smb_logon_script, smb_profile_path, "
"smb_profile_path, and smb_home_drive is not supported "
"by your IPA version")
# Check if IdP support is available
require_idp = (
idp is not None
or idp_user_id is not None
or userauthtype == "idp"
)
has_idp_support = ansible_module.ipa_command_param_exists(
"user_add", "ipaidpconfiglink"
)
if require_idp and not has_idp_support:
ansible_module.fail_json(
msg="Your IPA version does not support External IdP.")
# Make sure user exists
res_find = find_user(ansible_module, name)
@@ -1235,12 +1428,16 @@ def main():
# Generate args
args = gen_args(
first, last, fullname, displayname, initials, homedir,
gecos,
shell, email, principalexpiration, passwordexpiration,
password, random, uid, gid, city, userstate, postalcode,
phone, mobile, pager, fax, orgunit, title, carlicense,
sshpubkey, userauthtype, userclass, radius, radiususer,
departmentnumber, employeenumber, employeetype,
preferredlanguage, noprivate, nomembers)
password, random, uid, gid, street, city, userstate,
postalcode, phone, mobile, pager, fax, orgunit, title,
carlicense, sshpubkey, userauthtype, userclass, radius,
radiususer, departmentnumber, employeenumber, employeetype,
preferredlanguage, smb_logon_script, smb_profile_path,
smb_home_dir, smb_home_drive, idp, idp_user_id, noprivate,
nomembers,
)
if action == "user":
# Found the user
@@ -1276,8 +1473,21 @@ def main():
ansible_module.fail_json(
msg="Last name is needed")
smb_attrs = {
k: args[k]
for k in [
"ipanthomedirectory",
"ipanthomedirectorydrive",
"ipantlogonscript",
"ipantprofilepath",
]
if k in args
}
for key in smb_attrs.keys():
del args[key]
commands.append([name, "user_add", args])
if smb_attrs:
commands.append([name, "user_mod", smb_attrs])
# Handle members: principal, manager, certificate and
# certmapdata
if res_find is not None:

View File

@@ -977,12 +977,14 @@ def main():
changed = 'Archived data into' in result['summary']
elif command == 'vault_retrieve':
if 'result' not in result:
# pylint: disable=W0012,broad-exception-raised
raise Exception("No result obtained.")
if "data" in result["result"]:
data_return = exit_args.setdefault("vault", {})
data_return["data"] = result["result"]["data"]
else:
if not datafile_out:
# pylint: disable=W0012,broad-exception-raised
raise Exception("No data retrieved.")
changed = False
else:
@@ -993,7 +995,7 @@ def main():
changed = True
except ipalib_errors.EmptyModlist:
result = {}
except Exception as exception:
except Exception as exception: # pylint: disable=broad-except
ansible_module.fail_json(
msg="%s: %s: %s" % (command, name, str(exception)))

View File

@@ -1,10 +1,10 @@
-r requirements-tests.txt
ipdb==0.13.4
pre-commit==2.20.0
flake8==5.0.3
flake8-bugbear==22.10.27
pylint==2.14.4
wrapt == 1.14.0
pydocstyle==6.0.0
yamllint==1.28.0
ansible-lint==6.6.1
flake8==6.0.0
flake8-bugbear
pylint==2.17.2
wrapt==1.14.1
pydocstyle==6.3.0
yamllint==1.32.0
ansible-lint

View File

@@ -33,15 +33,16 @@ Supported Distributions
-----------------------
* RHEL/CentOS 7.6+
* CentOS Stream 8+
* Fedora 26+
* Ubuntu
* Ubuntu 16.04 and 18.04
Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -6,7 +6,7 @@ galaxy_info:
description: A role to backup and restore an IPA server
company: Red Hat, Inc
license: GPLv3
min_ansible_version: "2.8"
min_ansible_version: "2.13"
platforms:
- name: Fedora
versions:

View File

@@ -24,15 +24,17 @@ Supported Distributions
-----------------------
* RHEL/CentOS 7.4+
* CentOS Stream 8+
* Fedora 26+
* Ubuntu
* Debian
Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

View File

@@ -132,7 +132,7 @@ def main():
else:
get_ca_certs(fstore, options, servers[0], basedn, realm)
changed = True
del os.environ['KRB5_CONFIG']
os.environ.pop('KRB5_CONFIG', None)
except errors.FileError as e:
module.fail_json(msg='%s' % e)
except Exception as e:

View File

@@ -123,7 +123,7 @@ def temp_kdestroy(ccache_dir, ccache_name):
"""Destroy temporary ticket and remove temporary ccache."""
if ccache_name is not None:
run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False)
del os.environ['KRB5CCNAME']
os.environ.pop('KRB5CCNAME', None)
if ccache_dir is not None:
shutil.rmtree(ccache_dir, ignore_errors=True)

View File

@@ -272,7 +272,7 @@ def main():
get_ca_cert(fstore, options, servers[0], basedn)
else:
get_ca_certs(fstore, options, servers[0], basedn, realm)
del os.environ['KRB5_CONFIG']
os.environ.pop('KRB5_CONFIG', None)
except errors.FileError as e:
module.fail_json(msg='%s' % e)
except Exception as e:

View File

@@ -6,7 +6,7 @@ galaxy_info:
description: A role to join a machine to an IPA domain
company: Red Hat, Inc
license: GPLv3
min_ansible_version: "2.8"
min_ansible_version: "2.13"
platforms:
- name: Fedora
versions:

View File

@@ -307,8 +307,7 @@ try:
else:
# IPA version < 4.4
raise Exception("freeipa version '%s' is too old" % VERSION)
raise RuntimeError("freeipa version '%s' is too old" % VERSION)
except ImportError as _err:
ANSIBLE_IPA_CLIENT_MODULE_IMPORT_ERROR = str(_err)

View File

@@ -27,15 +27,16 @@ Supported Distributions
-----------------------
* RHEL/CentOS 7.6+
* CentOS Stream 8+
* Fedora 26+
* Ubuntu
* Ubuntu 16.04 and 18.04
Requirements
------------
**Controller**
* Ansible version: 2.8+
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)

Some files were not shown because too many files have changed in this diff Show More