Compare commits

...

233 Commits

Author SHA1 Message Date
Thomas Woerner
c251632368 Merge pull request #867 from jpclipffel/master
ipaclient: Removed invalid call `logger.info()`
2022-07-28 14:25:20 +02:00
Varun Mylaraiah
452d20e28d Merge pull request #866 from t-woerner/sid_generation_always
ipaserver/ipareplica: Always generate SIDs
2022-07-28 17:35:23 +05:30
jpclipffel
c7699472a6 ipaclient: Removed invalid call logger.info()
- Call was responsible for a `TypeError` exception
- Call was not useful (already followed by a proper `logger.warning` call)

Should fix issue #865: https://github.com/freeipa/ansible-freeipa/issues/865
2022-07-27 16:16:10 +02:00
Thomas Woerner
eba457d5ff ipaserver/ipareplica: Always generate SIDs
The SID is always generated in the command line installers in newer IPA
versions. This also needs to be done in the ipaserver and ipareplica roles.

For the IPA versions that are supporting this, the adtrust setup is always
executed to generated the SIDs, but only configures AD trust if
ipaserver_setup_adtrust or ipareplica_setup_adtrust is also enabled. A
check has been added to ipaserver_test and ipareplica_test to only enable
the SID generation for the IPA versions supporting this.

This is related to https://pagure.io/freeipa/8995

Fixes:
- https://bugzilla.redhat.com/show_bug.cgi?id=2110478
- https://bugzilla.redhat.com/show_bug.cgi?id=2110491
2022-07-27 15:01:17 +02:00
Varun Mylaraiah
809e423947 Merge pull request #864 from t-woerner/fix_RSN_always_on
ipaserver,ipareplica: Fix Random Serial Numbers always enabled
2022-07-25 20:25:39 +05:30
Thomas Woerner
e5f0ab2fe4 Merge pull request #863 from rjeffman/ipadnsconfig_fix_idempotency
ipadnsconfig: Fix boolean values comparison
2022-07-25 16:54:08 +02:00
Thomas Woerner
f85c60676c ipaserver,ipareplica: Fix Random Serial Numbers always enabled
The option _random_serial_numbers was using with the wrong type in
ipaserver_setup_ca.py and ipareplica_setup_ca.py. Therefore RSN was
always enabled.

Fixes:
- https://bugzilla.redhat.com/show_bug.cgi?id=2110523
- https://bugzilla.redhat.com/show_bug.cgi?id=2110526
2022-07-25 16:31:31 +02:00
Rafael Guterres Jeffman
f9bf0cfec0 ipadnsconfig: Disable only tests that are failing due to python-dns
This patch disables only the tests that are failing due to python-dns
issue in FreeIPA, allowing other tests in the test suite to be
executed.
2022-07-21 00:49:11 -03:00
Rafael Guterres Jeffman
8f0d983845 ipadnsconfig: Separate tests for forwarders with custom ports.
Due to an issue with python-dns, FreeIPA is raising an expection when
setting a DNS forwarder with a custom port. Separating the test for
ipadnsconfig that use forwarders with custom allows the other tests
to be correctly executed.
2022-07-21 00:46:33 -03:00
Rafael Guterres Jeffman
aed5edae33 ipadnsconfig: Enable chech_mode support 2022-07-21 00:46:01 -03:00
Rafael Guterres Jeffman
889b2a5576 ipadnsconfig: Fixe comparison of bool values in IPA 4.9.10+
IPA 4.9.10+ handles LDAP boolean values correctly, and the comparison
should be executed with the values itself, instead of a string
representation.
2022-07-21 00:42:52 -03:00
Thomas Woerner
e9d637c57a Merge pull request #854 from rjeffman/ci_enable_fedora_rawhide
upstream CI: enable tests on Fedora Rawide.
2022-07-08 18:12:12 +02:00
Thomas Woerner
b3a97eacec Merge pull request #850 from rjeffman/tests_allow_sanity_with_podman
sanity.sh: Allow use of podman instead of docker
2022-07-08 18:11:00 +02:00
Rafael Guterres Jeffman
aa745100e3 Merge pull request #859 from t-woerner/use_tasks_parse_ipa_version
ansible_freeipa_module: Use ipaplatform.tasks.parse_ipa_version
2022-07-08 12:47:06 -03:00
Rafael Guterres Jeffman
23faa83a0b sanity.sh: Allow use of podman instead of docker
When running tests/sanity/sanity.sh locally, podman might be available
instead of Docker. Due to current configuration, only Docker is used by
sanity.sh.

This patch searches for the availability of docker, which is kept as
the default container engine to use, and use podman only if docker is
not found.

This change also allows the execution of the script from a directory
other than the repository root.
2022-07-08 11:53:25 -03:00
Thomas Woerner
12729fc2c0 ansible_freeipa_module: Use ipaplatform.tasks.parse_ipa_version
api_check_ipa_version was using packaging.version. IPA is using
pkg_resources.parse_version in ipaplatform.tasks.parse_ipa_version.

With this change tasks.parse_ipa_version from ipaplatform is used to
have exactly the same version comparison that also IPA has.

Additionally tasks is added to __all__.
2022-07-08 14:58:44 +02:00
Rafael Guterres Jeffman
31810ad7c0 upstream CI: enable tests on Fedora Rawide.
This patch enable upstream CI to build a testing Fedora Rawhide
container and enables its use in nightly and weekly test runs.
2022-07-07 10:53:54 -03:00
Thomas Woerner
9dcff9a308 Merge pull request #851 from rjeffman/dnszone_fix_bool_behavior
Fix handling of boolean values for FreeIPA 4.9.10+
2022-07-06 20:44:13 +02:00
Rafael Guterres Jeffman
e500c133c0 Merge pull request #856 from t-woerner/argspec
Provide own getargspec for roles and modules with Python 3.11
2022-07-06 12:51:26 -03:00
Rafael Guterres Jeffman
a5306b2db5 pytests/test_dnszone: Fix evaluation of boolean values
Evaluating boolean values output by FreeIPA must use regular
expressions to handle both "TRUE/FALSE" and "True/False".
2022-07-06 12:11:16 -03:00
Rafael Guterres Jeffman
8ab3aa06ff pytest tests: Enhanced assertion for check_* methods.
Checking if some output is present or absent from standard streams was
done by simple string searching. Due to recent changes in FreeIPA, this
search is not effective due to capitalization differences in boolean
values output. Changing the string searching to regular expression
searches fixes this behavior for current and previous versions of
FreeIPA.

This patch also adds more information on the assert tests in case of an
error, so that it is easier to understand why the test failed.
2022-07-06 12:11:16 -03:00
Rafael Guterres Jeffman
87ff15a92c api_check_ipa_version: Fix version comparison for more than one digit
The fallback function used to compare IPA versions was spliting the
version string into a tuple of strings, and the comparison of the tuple
would fail if comparing a field with one digit aginst a two-digit one,
for example, '8' with '10', as the string comparison would put '10'
before the '8'.

This patch forces the version fields to be converted to integers, so
a numerical comparison will be performed. If a version string field
cannot be converted to a number, than the string comparison will still
be used.
2022-07-06 12:11:16 -03:00
Rafael Guterres Jeffman
c8d5cb7ee2 Fix handling of boolean values for FreeIPA 4.9.10+
FreeIPA 4.9.10+ and 4.10 use proper mapping for boolean values, and
only searching for "TRUE" does not work anymore.

This patch fix ipadnszone plugin and IPAParamMapping class handling
of boolean values.
2022-07-06 12:11:16 -03:00
Rafael Guterres Jeffman
2fa4aa60b1 Merge pull request #857 from t-woerner/server_test_use_fqdn
tests/server/test_server.yml: Fix generation of ipaserver_domain
2022-07-06 10:41:38 -03:00
Rafael Guterres Jeffman
4332636fd2 Merge pull request #852 from t-woerner/rsn_missing
ipaserver,ipareplica: Add random_serial_numbers to options
2022-07-06 10:06:51 -03:00
Thomas Woerner
266f79b55f tests/server/test_server.yml: Fix generation of ipaserver_domain
The generation of ipaserver_domain has issues: At first
ansible_facts['hostname'] instead of ansible_facts['fqdn'] is used
and second the first entry after the split operation is used and third
the final join is missing.
2022-07-06 12:43:49 +02:00
Thomas Woerner
07b056ad25 Provide own getargspec for roles and modules with Python 3.11
Python 3.11 dropped compat inspect.getargspec. As the roles and modules
need to support Python2 and Python3, the code for getargspec has been
copied from Python 3.10 and is added as a fallback as soon as getargspec
can not be imported from inspect. The copied getargspec is using
getfullargspec internally.

Fixes: #855 (Python's inspect.getargspec was removed in version 3.11)
2022-07-06 11:25:49 +02:00
Thomas Woerner
7db5d59de1 ipaserver,ipareplica: Add random_serial_numbers to options
With the support for Random Serial Numbers v3 in FreeIPA 4.10, the
attribute random_serial_numbers has been added to the installer options.

options._random_serial_numbers is generated by ca.install_check and
later used by ca.install in the _setup_ca module.

ca.install_check is using options.random_serial_numbers and generating
options._random_serial_numbers which is later used by ca.install in
ca.install the _setup_ca module.

Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=2103928
       https://bugzilla.redhat.com/show_bug.cgi?id=2103924
2022-07-06 09:31:41 +02:00
Rafael Guterres Jeffman
e19e16c734 Merge pull request #853 from t-woerner/concatenation_only_with_jinja
ipaserver: Use jinja for list concatenation
2022-07-05 12:06:05 -03:00
Thomas Woerner
0ff119a2a8 ipaserver: Use jinja for list concatenation
With ansible-2.13 it is required to use jinja for list concatenation.

  list: "[] + ['a'] + ['b']"

needs to become

  list: "{{ [] + ['a'] + ['b'] }}"

copy_external_cert.yml needed to be changed.
2022-07-05 16:42:23 +02:00
Thomas Woerner
90f6e14c40 Merge pull request #844 from rjeffman/ci_enable_ansible_core_2_12
upstream CI: Update nightly Ansible versions.
2022-06-24 12:26:00 +02:00
Rafael Guterres Jeffman
e044310dad upstream CI: Enable tests using ansible-core 2.12.
As the current latest upstream version of ansible-core is 2.13.0, to
test against ansible-core 2.12 series we need to pin the version used
on the test.

This patch enables the already defined tests for ansible-core 2.12 that
were available but commented out.
2022-06-23 13:19:17 -03:00
Rafael Guterres Jeffman
4be7a9fba0 upstream CI: Remove Ansible 2.9 from test matrix
Ansible 2.9 is EOL, and we should only test with supported upstream
versions of Ansible.

This patch removes tests against Ansible 2.9.
2022-06-23 13:19:17 -03:00
Thomas Woerner
98959807d2 Merge pull request #825 from rjeffman/ci_test_galaxy_collection
upstream CI: Add support for testing ansible-freeipa as a collection.
2022-06-23 18:01:42 +02:00
Thomas Woerner
a16379cfa0 Merge pull request #832 from rjeffman/idrange_fix_absent_invalid
idrange: Fix list of invalid parameters for 'state:absent'.
2022-06-23 17:59:04 +02:00
Thomas Woerner
672413f4dd Merge pull request #831 from rjeffman/idrange_fix_dom_name
idrange: Fix usage of dom_name when idrange doesn't exist.
2022-06-23 17:58:29 +02:00
Rafael Guterres Jeffman
8af4329fac Merge pull request #838 from t-woerner/smartcard_roles
New roles for smartcard server and client setup
2022-06-23 09:28:47 -03:00
Thomas Woerner
9932b1dc98 New roles for smartcard server and client setup
There are new smartcard roles in the roles folder:

    roles/ipasmartcard_server
    roles/ipasmartcard_client

This roles allows to setup smartcard for servers and clients.

Here is the documentation for the roles:

    roles/ipasmartcard_server/README.md
    roles/ipasmartcard_client/README.md

New example playbooks have been added:

    playbooks/install-smartcard-server.yml
    playbooks/install-smartcard-replicas.yml
    playbooks/install-smartcard-servers.yml
    playbooks/install-smartcard-clients.yml
2022-06-22 15:13:52 +02:00
Rafael Guterres Jeffman
1c44898e68 idrange: Fix list of invalid parameters for 'state:absent'.
As an idrange has no members, when using `state: absent`, all
parameters but 'name' and 'state' are invalid. The list of invalid
parameters when 'state: absent', have been fixed to include some
missing parameters.
2022-06-21 11:35:20 -03:00
Rafael Guterres Jeffman
f44dc55b90 upstream CI: Add support for testing ansible-freeipa as a collection.
Provide a pipeline to test ansible-freeipa as an Ansible Galaxy
collection. The tests will use 'utils/build-galaxy-release.sh' to
create the galaxy release file, install it as a collection, and run
the tests in it, which were modified to use FQCN.

The tests will run only on 'fedora-latest' for each PR, and on all
platforms for nightly and weekly tests.
2022-06-21 10:40:21 -03:00
Thomas Woerner
65b106449e Merge pull request #833 from rjeffman/idrange_fix_typo
idrange: Fix typo in test comments.
2022-06-21 12:56:23 +02:00
Thomas Woerner
7501c84844 Merge pull request #841 from rjeffman/requirements_virtualenv
requirements-dev: Update requirements for virtual environments
2022-06-21 12:55:34 +02:00
Rafael Guterres Jeffman
d45e6ac399 pylint: Ignore module ipaserver.dcerpc errors.
When evaluating imports, pylint does not have access to IPA imports,
so they need to be ignored during import or usage.
2022-06-20 15:34:27 -03:00
Rafael Guterres Jeffman
d990832681 idrange: Fix addition of idrange with dom_name.
When ensuring presence of an idrange using dom_name instead of dom_sid,
the SID must be obtained so that the idrange can be created.

Related to RHBZ#2086993 and RHBZ#2086994.
2022-06-17 10:21:05 -03:00
Rafael Guterres Jeffman
b998597815 ansible_module_utils: add method to retrive SID from dom_name.
When managing idranges, it might be needed to obtain the domain SID
from the domain name. As this method needs to use the IPA API object
and requires imorting some ipaserver modules, teh best place for this
method to be implemented is on ansible_module_utils.
2022-06-17 10:21:05 -03:00
Rafael Guterres Jeffman
d51ee9dc69 requirements-dev: Update requirements for virtual environments
When developing ansible-freeipa using a Python virtual environment,
some ansible-freeipa utility scripts failed to execute due to missing
tools.

This patch add the required tools and modules to requirements-dev.txt
and pin the versions to the same available in Fedora 36.
2022-06-17 10:16:49 -03:00
Thomas Woerner
fdfea1b6fb Merge pull request #354 from rjeffman/tests_ignore_tests
Add support to define which playbook tests to execute with pytest.
2022-06-15 19:50:31 +02:00
Rafael Guterres Jeffman
ac92ed1408 fixup! Add support to define which playbook tests to execute with pytest. 2022-06-15 09:53:32 -03:00
Rafael Guterres Jeffman
757b89dfae upstream tests: Disable dnsconfig and dnsforwardzone
Due to an issue with IPA in Fedora 36, dnsconfig and dnsforwardzone
plugin tests must be disabled.

See FreeIPA issue: https://pagure.io/freeipa/issue/9158
2022-06-14 21:43:05 -03:00
Rafael Guterres Jeffman
914e4879f8 tests/utils.py: Fix pylint issues. 2022-06-14 21:43:05 -03:00
Rafael Guterres Jeffman
13cff6354b Add support to define which playbook tests to execute with pytest.
pytest provide the means to skip tests based on patterns, but writing
these patterns for ansible-freeipa might not be feasible.

This PR allows the selection of playbook tests and modules that will
be executed with pytest using the environmentt variables IPA_ENABLED_TESTS
IPA_ENABLED_MODULES, IPA_DISABLED_TESTS or IPA_DISABLED_MODULES.

When using IPA_ENABLED_MODULES, all modules will be disabled, and only
the modules in the enabled list will be tested. If using the test
filter, IPA_ENABLED_TESTS, all tests are disabled, unless they are in
the enabled test lists.

If the IPA_DISABLED_* version is used, tests and modules are enabled by
default, and the list is used to disable the module or specific test.

To disable a test or module in Azure CI, edit the file
`tests/azure/variables` and add the desired tests or modules to the
parameter variables `enabled_modules`, 'enabled_tests`, `disabled_tests`
or `disable_modules`.

Note that, if added to the `master` branch, this will affect the tests
for every pipeline that it is include (including 'nightly'), so it should
be used with care.

It can be used with TEMP commits to enable only the desired tests,
speeding up upstream tests.
2022-06-14 21:23:18 -03:00
Thomas Woerner
4ff5aaa172 Merge pull request #830 from rjeffman/ci_fix_missing_changelog
Fix ansible-test sanity missing CHANGELOG.rst.
2022-06-14 15:40:20 +02:00
Rafael Guterres Jeffman
d82abdbef9 build-galaxy-release: Automatically create CHANGELOG.
Recent versions of ansible-test require the existence of a CHANGELOG
file in the root of the collection. This changes extracts the changes
of the latest available release tag using `utils/changelog` and create
the CHANGELOG file with the result of the command.

The generated changelog will include the changes for the latest release
and, if present, the available changes that were not part of a release.
2022-06-14 10:33:08 -03:00
Rafael Guterres Jeffman
5aa80204d5 Merge pull request #842 from t-woerner/changelog_for_galaxy
utils/changelog: Fixed --tag option, new --galaxy option
2022-06-14 10:21:25 -03:00
Thomas Woerner
8b8cbdd8c2 utils/changelog: Fixed --tag option, new --galaxy option
The --tag TAG option is now printing the changes for the given TAG and
not since the given tag. The new option --galaxy is printing the changelog
since the latest tag and also for the latest tag.

These changes are simplifying the generation of the changelog file that
is needed to pass the tests for galaxy and AutomationHub collections.
2022-06-14 15:07:11 +02:00
Thomas Woerner
a06b16f5bc Merge pull request #827 from rjeffman/ci_update_ansible
Upstream CI updates.
2022-06-14 12:31:52 +02:00
Rafael Guterres Jeffman
dc99b821eb idrange: Fix typo in test comments.
There were some typos in the idrange test playbook.
2022-05-23 08:39:27 -03:00
Rafael Guterres Jeffman
796f84357a upstream CI: Update default ansible-core version to 2.12.
The current ansible-core available in Fedora and RHEL is 2.12 series.
This patch sets the version used for every PR CI to match this series.

Other versions should be used only in the nightly/weekly tests.
2022-05-12 14:50:32 -03:00
Rafael Guterres Jeffman
9e6c79abbb upstream CI: Allow the use of latest ansible-core.
This patch adds the latest ansible-core as a test target in upstream
nightl/weekly CI.

As, currently, the latest available ansible-core is still 2.12.z, the
current ansible-core 2.12 targets were disabled. They should be enabled
when ansible-core 2.13 is available.
2022-05-12 14:50:32 -03:00
Rafael Guterres Jeffman
d3af87c731 upstream CI: removed all CentOS 8 support.
CentOS 8 images are not supported anymore, and we are using CentOS 8
Stream images.

This patch removes all configuration for CentOS 8 and updates test
README to point to the available container images.
2022-05-12 14:50:32 -03:00
Rafael Guterres Jeffman
7011283335 upstream CI: Relabel upstream PR pipeline jobs.
As Ansible versions might change, and as we don't need to report which
version is used on every test, as the information is avaiable in case
it is needed, the jobs labels are changed to easier display which image
was used for testing.
2022-05-12 14:50:32 -03:00
Rafael Guterres Jeffman
0297cbe973 Merge pull request #829 from t-woerner/build-galaxy-release_with_install
utils/build-galaxy-release.sh: Add "-i" to install generated collection
2022-05-12 12:12:15 -03:00
Thomas Woerner
1ec0d1e640 utils/build-galaxy-release.sh: Add "-i" to install generated collection
The "-i" option can be used to install the generated collection using
the ansible-galaxy collection install command. It is using the force
flag to install the collection if there is already a collection with the
same name and namespace. The ansible-galaxy collection build command is
already using the force flag to create the collection.
2022-05-12 15:06:39 +02:00
Thomas Woerner
ba3fe74b60 Merge pull request #487 from rjeffman/ipagroup_add_idoverrideuser
Add support for managing idoverrideusers in ipagroup.
2022-04-29 13:39:33 +02:00
Thomas Woerner
b9151f3069 Merge pull request #813 from rjeffman/idrange
New idrange management module
2022-04-29 13:35:32 +02:00
Thomas Woerner
6085fbf77d Merge pull request #820 from rjeffman/ipaautomountmap_mapname_required
ipaautomountmap: Fix parameter evaluation.
2022-04-29 13:16:37 +02:00
Rafael Guterres Jeffman
603bd61845 New idrange management module
There is a new idrange management module placed in the plugins folder:

    plugins/modules/ipaidrange.py

The idrange module allows to ensure presence and absence of idranges.

Here is the documentation of the module:

    README-idrange.md

New example playbooks have been added:

    playbooks/idrange/idrange-absent.yml
    playbooks/idrange/idrange-ad-posix-present.yml
    playbooks/idrange/idrange-ad-present.yml
    playbooks/idrange/idrange-present.yml

New tests for the module can be found at:

    tests/idrange/test_idrange.yml
    tests/idrange/test_idrange_client_context.yml
2022-04-28 11:54:41 -03:00
Rafael Guterres Jeffman
1a31f62a6f ipaautomountmap: Fix error messages for invalid 'name' sizes.
This patch fixes the error messages when an invalid number of 'mapname'
are provided for states 'present' or 'absent'.
2022-04-27 11:26:32 -03:00
Rafael Guterres Jeffman
23e07a9a17 ipaautomountmap: Force setting automountmapname in IPA API calls.
The usage of 'automountmapname' is required in all automount map IPA
API calls, and this change ensures that the value is always set as
an argument.
2022-04-27 11:25:39 -03:00
Thomas Woerner
bd084ad37b Merge pull request #810 from rjeffman/ipatrust_fix_range_type
ipatrust: fix range_type and test enhancement.
2022-04-27 15:36:16 +02:00
Rafael Guterres Jeffman
099eb96b58 Add support for managing idoverrideusers in ipagroup.
The group CLI option `idoverrideusers` was not supported by
ansible-freeipa, and this patch adds support to it.

Tests require an AD trust, and a user `aduser@ad.ipa.test` to exist, or
the user name must be provided (variable, CLI)  through `test_ad_user`.

A new test playbook was added:

    tests/group/test_group_idoverrideuser.yml
2022-04-27 07:41:47 -03:00
Thomas Woerner
1276e38895 Merge pull request #780 from rjeffman/module_utils_empty_strings_and_inexistent_attributes
module_utils: Fix comparison of elements not in IPA object.
2022-04-27 08:29:38 +02:00
Thomas Woerner
2fa9ed9127 Merge pull request #808 from rjeffman/ipatrust_type_choices
ipatrust: Set valid choices for trust_type.
2022-04-27 08:28:20 +02:00
Rafael Guterres Jeffman
766cf5a285 ipatrust: Fix support for range_type.
The ipatrust module was ignoring the value of `range_type`, which is
required to allow for different types of idranges.
2022-04-26 14:43:05 -03:00
Rafael Guterres Jeffman
3ea452ef6f tests/trust: Improved test coverage and execution.
This patch applies several changes to the ipatrust test playbook:

* Add externally defined parameters so execution in local trust
  environments can be configured. The available parameters are:
    * winserver_admin_password: the Administrator password for the AD
      server (default: 'SomeW1Npassword')
    * winserver_domain: the AD server domain (default: 'windows.local')
    * winserver realm: the AD server realm (by default, the uppercase
      version of winserver_domain)
    * ipaserver_domain: the FreeIPA server domain (default: 'ipa.test')
    * ipaserver_realm: the FreeIPA server realm (by default, the
      uppercase version of ipaserver_domain

* Modify trust verification to check for the existence of the trust as
  it the output of `ipa trust-find`, instead of cheking for the number
  of items returned, as the number might vary.

* Add idempotency tests by re-executing tasks and verifying that no
  change was performed.

* Added tests to verify creation of trusts with different 'range_type'.

* Use a Kerberos cache for shell scripts, and destroy it on exit.

* Properly remove all `idrange` that might be created upon setting up a
  trust.
2022-04-26 14:43:05 -03:00
Rafael Guterres Jeffman
50b16cb33f tests/ipatrust: Modify AD realm name to an invalid name.
As the task is expected to fail, the AD realm name was modified to show
the expected behavior more clearly.
2022-04-26 14:42:40 -03:00
Thomas Woerner
9b0558a953 Merge pull request #807 from rjeffman/zone_forwarder_consistency
DNS forward policy: ensure consistency between module parameters.
2022-04-26 17:17:26 +02:00
Rafael Guterres Jeffman
6124dc0cf1 ipatrust: Updated ipatrust documentation.
This patch updates the ipatrust documentation about the 'trust_type'
parameter, and changes one password to be similar to the standard
passwords used in other modules.
2022-04-26 11:12:55 -03:00
Rafael Guterres Jeffman
423a6b0e12 ipatrust: Set valid choices for trust_type.
Ensure only valid choices for trust_type ('ad')  are available for the
module parameter.
2022-04-26 11:12:55 -03:00
Rafael Guterres Jeffman
a83bab9425 ipaautomountmap: Allows clearing description attribute with "".
This change allows clearing automountmap 'description' attribute by
passing an empty string ("") as the playbook parameter.

New test cases were added to check this behavior.
2022-04-26 09:58:01 -03:00
Rafael Guterres Jeffman
70f4b7d646 ipauser: Refactor module due to fix on arguments comparison.
Due to a change in 'ansible_freeipa_module.compare_args_ipa', playbook
parameters using empty strings are correctly evaluated, and do not need
to be removed before comparison is performed.

A new test playbook, with tests for clearing attributes with an empty
string ("") is available at:

    tests/user/test_user_empty_lists.yml
2022-04-26 09:58:01 -03:00
Rafael Guterres Jeffman
f2865efb1a module_utils: Fix comparison of elements not in IPA object.
This change modifies the comparison of the retrieved IPA object and the
provided arguments on ansible_freeipa_module.compare_args_ipa when the
provider argument is an empty string.

If an attribute is not available in 'ipa', its value is considered to be
a list with an empty string (['']), possibly forcing the conversion of
the 'args' attribute to a list for comparison. This allows, for example,
the usage of empty strings which should compare as equals to inexistent
attributes (None), as is done in IPA API.
2022-04-26 09:58:01 -03:00
Thomas Woerner
ce143bad52 Merge pull request #805 from rjeffman/templates_add_password_example_playbooks
utils/new_module templates: Add missing password to example playbooks.
2022-04-26 12:39:18 +02:00
Thomas Woerner
928fdf4b2d Merge pull request #757 from rjeffman/templates_refactor
Update module templates to current practices.
2022-04-26 12:36:46 +02:00
Thomas Woerner
0d95b8ebcb Merge pull request #818 from rjeffman/ansible_lint_tasks
ansible-lint: Identify env_*.yml and tasks_*.yml as task files.
2022-04-26 12:28:43 +02:00
Rafael Guterres Jeffman
0efe2c30d2 ansible-lint: Identify env_*.yml and tasks_*.yml as task files.
Failing to identify task files included by playbooks raised false
positives when runnnig ansible lint. This change force ansible-lint to
correctly identify YAML files named "env_*.yml" or "tasks_*.yml" as task
files that are imported by other playbooks, and treat them accordingly.
2022-04-25 10:58:16 -03:00
Rafael Guterres Jeffman
10e9c30af6 DNS forward policy: ensure consistency between module parameters.
Modules ipadnsconfig and ipadnsforwardzone allow the setting of forward
policy for zone forwarders, but the parameter names differ between the
modules.

This patch ensures that the same parameter names can be used in each
module. To keep backwar compatibility in both modules, both
`forward_policy` and `forwardpolicy` are now supported.
2022-04-12 15:53:33 -03:00
Rafael Guterres Jeffman
f770b5d581 utils/new_module templates: Add missing password to example playbooks.
Add missing ipaadmin_password to example playbooks so new modules have
all necessary fields set on basic files.
2022-04-11 18:06:57 -03:00
Rafael Guterres Jeffman
9b020a56f3 Merge pull request #799 from vjs2174/master
Update README-group.md
2022-04-06 21:02:26 -03:00
vjs2174
09a0077b77 Update README-group.md
Fixed issue #790 changed line 103 to be more accurate.
2022-04-06 14:08:58 -04:00
Thomas Woerner
3779698e0a Merge pull request #793 from rjeffman/playbooks_minor_fixes
Ensure example playbooks have ipaadmin_password and it is the standard one.
2022-04-05 13:20:27 +02:00
Thomas Woerner
65adc7860e Merge pull request #791 from rjeffman/pylint_update_2_12_2
Update pylint to version 2.12.2
2022-04-05 13:19:28 +02:00
Rafael Guterres Jeffman
df87ff464a example playbooks: ipaadmin_password is used and consistent.
Some example playbooks do not had the parameter `ipaadmin_password`
set, and some had a different value than the standard value
"SomeADMINpassword".

This patch fixes this difference in all example playbooks.
2022-03-30 08:45:05 -03:00
Rafael Guterres Jeffman
4b8358b897 Removed vim swap file from the repository. 2022-03-30 08:44:58 -03:00
Rafael Guterres Jeffman
68661d6922 pylint: Bump version to 2.12.2.
Update pylint version to the latest supported by Fedora 36.
2022-03-22 12:03:20 -03:00
Rafael Guterres Jeffman
461bd8b15b pylint: Ignore global-variable-not-assigned 2022-03-22 12:03:20 -03:00
Rafael Guterres Jeffman
ef0e368741 pylint: Ignore consider-using-f-string.
Newer versions of pylint warns about not using f-strings, but those are
not supported in Python 2, which ansible-freeipa still need to support.
2022-03-22 11:25:13 -03:00
Varun Mylaraiah
f0a71eda84 Merge pull request #779 from t-woerner/module_params_get_fail_empty_str_in_list
module_params_get*: Fail on empty string in string list parameters
2022-03-03 18:36:53 +05:30
Rafael Guterres Jeffman
d0402d7905 Merge pull request #783 from t-woerner/automember_remove_debug_warn
automember: Remove debug output
2022-02-28 12:49:22 -03:00
Thomas Woerner
eebfdbca7a automember: Remove debug output
The warn debug line was added with "Add automember default group
handling" d2648b142a
2022-02-28 13:16:22 +01:00
Thomas Woerner
e30bcfd876 ipaconfig: Set allow_empty_string for user_auth_type, pac_type, configstring
The parameters user_auth_type, pac_type and configstring are allowing to
use "" to reset to the default value or for configstring to set an empty
list.

The new check in params_get is not allowing to use empty strings in lists,
therefore allow_empty_string=True had to be added to the call.

A test has been added to verify that the empty strings are supported and
working.

Additionally empty pac_type, user_auth_type and domain_resolution_order
have been added to exit_args as if they have not been set.
2022-02-28 13:12:41 +01:00
Thomas Woerner
abf0cc3251 ipahost: Set allow_empty_string for auth_ind
The parameter auth_ind is allowing to use "" to reset to the default
value.

The new check in params_get is not allowing to use empty strings in lists,
therefore allow_empty_string=True had to be added to the call.

A test has been added to verify that the empty strings are supported and
working.
2022-02-25 18:42:25 +01:00
Thomas Woerner
9decad4e4f ipaservice: Set allow_empty_string for auth_ind and pac_type
The parameters auth_ind and pac_type are allowing to use "" to reset to
the default value.

The new check in params_get is not allowing to use empty strings in lists,
therefore allow_empty_string=True had to be added to the call.

A test has been added to verify that the empty strings are supported and
working. An idempotency issue with pac_type has been found with the test
and fixed additionally.
2022-02-25 18:42:07 +01:00
Thomas Woerner
03098c218d ipauser: Set allow_empty_string for userauthtype and sshpubkey
The parameters userauthtype and sshpubkey allowing to use "" to reset to
the default value.

The new check in params_get is not allowing to use empty strings in lists,
therefore allow_empty_string=True had to be added to the call.

A test has been added to verify that the empty strings are supported and
working. An idempotency issue with sshpubkey has been found with the test
and fixed additionally.
2022-02-24 12:37:55 +01:00
Thomas Woerner
d05ad6b1f2 module_params_get*: Fail on empty string in string list parameters
So far it is possible to pass list parameters with empty strings to the
modules. The use of empty strings in list does not make a lot of sense,
though. The simple solution is to add a check to module_params_get for
empty strings in returned lists.

The option allow_empty_string can be set to True to allow an empty string
in the list with a list len of 1. The option defaults to False. It is
needed for some parameters the modules, like for example userauthtype in
the user module. It is using "" to reset to the default value.

module_params_get_lowercase has been changed to use module_params_get to
have one place to add the check.

Due to an issue in Ansible it is possible to use the empty string "" for
lists with choices, even if the empty list is not part of the choices.
Ansible issue https://github.com/ansible/ansible/issues/77108
2022-02-24 12:37:42 +01:00
Thomas Woerner
9981e5f84b Merge pull request #752 from rjeffman/hbacrule_allow_clearing_members
hbacrule: Allow clearing members with empty lists.
2022-02-21 15:01:29 +01:00
Rafael Guterres Jeffman
4df2cab42a module templates: Add delete_commit code template.
This patch add the lines necessary to allow the use of the attribute
`delete_continue`, as it is a commom attribute, and if newer commom
attributes are added to IPAAnsibleModule in the future, the usage will
be similar.
2022-02-17 15:22:18 -03:00
Rafael Guterres Jeffman
5d6324e2da module templates: Add example and note for case insensitive members.
Some modules should be compared in a case insensitive manner, and this
patch adds an example of a call to IPAAnsibleModule.params_get_lowercase
and a note on its usage.
2022-02-17 14:31:48 -03:00
Rafael Guterres Jeffman
8772379dcc module templates: Refactor member management.
This patch refactors the module template for modules with member
management, in a way that the addition of member management command
logic is not duplicated in different states or actions.

This idiom has been applied recently along with other fixes to modules
with idempotence issues reducing the modules code size and centering
code logic in specific blocks.
2022-02-17 14:31:48 -03:00
Thomas Woerner
29badaecca Merge pull request #761 from rjeffman/delete_continue_module_utils
IPAAnsibleModule: Provide base configuration for delete_continue.
2022-02-17 16:01:12 +01:00
Rafael Guterres Jeffman
e88aaaf95a IPAAnsibleModule: Provide base configuration for delete_continue.
Allows the creation of IPAAnsibleModule objects with specific
`ipa_arguments` which are defined in a dictionary of argumets in
the base class.

Every module using `delete_continue` should provide the proper behavior
and the module must be instantiated with:

  ansible_module = IPAAnsibleModule(
      ...,
      ipa_arguments=["delete_continue"]
  )

The plugin documentation must be extended with
'ipamodule_arguments.delete_continue'.
2022-02-17 08:20:57 -03:00
Thomas Woerner
b54333358d Merge pull request #777 from rjeffman/ci_fix_c8s_usage
upstream ci: Fix scenario for Centos 8 Stream with Ansible 2.11.
2022-02-16 22:13:09 +01:00
Thomas Woerner
c16ceac892 Merge pull request #770 from rjeffman/ci_rename_c9s_pipelines
upstream ci: Rename CentOS 9 pipelines jobs to c9s.
2022-02-16 20:39:31 +01:00
Rafael Guterres Jeffman
d303a81e4c upstream ci: Fix scenario for Centos 8 Stream with Ansible 2.11.
Changed scenario from old CentOS 8 (centos-8) to current Centos 8
Stream (c8s).
2022-02-16 14:50:48 -03:00
Rafael Guterres Jeffman
d561d8f372 upstream ci: Rename CentOS 9 pipelines jobs to c9s.
The correct name for upcoming release of CentOS is CentOS 9 Stream,
usually abbreviated to 'c9s'. As we need to differentiate from the
stream and the standard versions, this patch modifies the Azure
piipelines to use 'c9s' instead of 'CentOS 9'.
2022-02-16 14:48:58 -03:00
Thomas Woerner
33c571ebb6 Merge pull request #776 from rjeffman/ci_fix_ansible_lint_dnsrecord
ansible-lint: Remove warning on 'ignore_errors'.
2022-02-16 16:10:01 +01:00
Thomas Woerner
81d1896f0f Merge pull request #775 from rjeffman/ci_build_container_python_version
upstream CI: Fix container builds in face of Ansible and CentOS changes.
2022-02-16 16:08:36 +01:00
Thomas Woerner
75f5082ad0 Merge pull request #732 from rjeffman/ci_enable_c8s
upstream CI: Enable CentOS 8 Stream for PR and nightly tests.
2022-02-16 12:05:22 +01:00
Thomas Woerner
a05eed6a4b Merge pull request #758 from rjeffman/ci_centos9_ansible_2_12
upstream ci: enable ansible-core 2.12 for CentOS 9 Stream.
2022-02-16 09:52:46 +01:00
Rafael Guterres Jeffman
cddb861fd9 ansible-lint: Remove warning on 'ignore_errors'.
The test for dnsrecord creates a DNSSEC zone, and was forcing the task
to ignore errors using `ignore_errors: true`. The test environment
should be clean at that point, and without the zone, tests would fail,
so there is no need to keep the attribute set. If the task fails, it
should be fixed.
2022-02-15 17:04:09 -03:00
Rafael Guterres Jeffman
15d3123ed3 Merge pull request #774 from t-woerner/no_molecule_prerun
molecule: Disable prerun for normal tests
2022-02-15 14:56:22 -03:00
Rafael Guterres Jeffman
7a1bf986a8 upstream CI: Use fedora-latest as default test container.
With the removal of CentOS 8 container, the available Fedora latest
image will be used for tests, if a specific container is not given.
2022-02-15 13:24:34 -03:00
Rafael Guterres Jeffman
c89f6624b5 upstream CI: Update Python version when building containers.
Newer Ansible versions will require at least Python 3.8 to be used,
and the build containers pipeline was requiring Python 3.6, which is
EOL.

This patch requests the latest Python version available for the
controller, and allows it to be configured to a specific version if,
and when, needed.
2022-02-15 13:19:39 -03:00
Rafael Guterres Jeffman
998a141482 upstream CI: Enable CentOS 8 Stream for PR and nightly tests.
Add configuration to build a testing CentOS 8 stream image and to
execute upstream tests using that image in pull requests (Ansible
2.9) and on the nightly tests (all supported Ansible versions).
2022-02-15 13:19:39 -03:00
Rafael Guterres Jeffman
d111f0d92b ci images: Fix creation of CentOS 9 stream test container.
CentOS 9 Stream package pytho3-devel was not installable, and as it is
not required for the testing container, it was removed from the
Dockerfile used to create the image.
2022-02-15 12:16:09 -03:00
Rafael Guterres Jeffman
5ab9ae21ad molecule: Disable prerun for build containers.
This disables the generation of the collection using the default
galaxy.yml. The installation of the generated collection fails with
invalid version A.B.C.

The collection is not used when building containers and the generated
collection is not using proper name and namespace in the collection files.
2022-02-15 10:11:00 -03:00
Rafael Guterres Jeffman
3c130795e3 build containers: Allow setting of Python version used.
Currently the pipeline used to create test containers is using Python
3.6.15, and Ansible 2.12 requires, at least, Python 3.8.

This change adds a new parameter to build container template,
`python_version`, which is set by default to '3.x', meaning it will use
the latest Python version available (for version 3) if the parameter is
not explicitly set.
2022-02-15 09:12:26 -03:00
Thomas Woerner
954c911a85 molecule: Disable prerun for normal tests
This disables the generation of the collection using the default
galaxy.yml. The installation of the generated collection fails with
invalid version A.B.C.

The collection is not used in the tests and the generated collection
is not using proper name and namespace in the collection files.

Note: utils/build-galaxy-releasesh needs to be used to generate the correct
collection.
2022-02-15 12:46:29 +01:00
Rafael Guterres Jeffman
e681f25e5c Merge pull request #773 from t-woerner/servicedelegation_do_no_fail_on_not_existing_members_with_state_absent
servicedelegation: Do not fail for not existing members with state absent
2022-02-14 18:10:46 -03:00
Thomas Woerner
8010d19be9 servicedelegation: Do not fail for not existing members with state absent
Ensuring absence of members (services and targets) that do not exist may
not fail as they are not members for servicedelegationtarget and
servicedelegationrule.

servicedelegation_normalize_principals in ansible_freeipa_module has
been extended with a check_exists argument that defaults to False. state
== "present" is now given as this argument to turn on the element exists
check only if elements should be added.
2022-02-14 18:16:29 +01:00
Rafael Guterres Jeffman
892cb037eb Merge pull request #771 from t-woerner/build-galaxy-release_fix_refs_for_all_doc_fragments
build-galaxy-release: Fix refs for all doc_fragments in plugins/doc_fragments
2022-02-14 11:50:58 -03:00
Rafael Guterres Jeffman
40d4150590 Merge pull request #772 from t-woerner/fix_new_ansible-lint_findings
Fix new ansible-lint findings
2022-02-14 11:50:25 -03:00
Thomas Woerner
bc72bbd92e tests/vault/test_vault_change_type.yml: Use lower case var names
The upper case name has been reported as issues by new ansible-lint.
2022-02-14 13:42:56 +01:00
Thomas Woerner
ae9c81139b tests/role/test_role_lists_handling.yml: Use lower case var names
The upper case name has been reported as issues by new ansible-lint.
2022-02-14 13:39:36 +01:00
Thomas Woerner
d5fdaaf444 tests/env_freeipa_facts.yml: Use lower case var names
The upper case name has been reported as issues by new ansible-lint.
2022-02-14 13:37:54 +01:00
Thomas Woerner
fdd4b19b18 tests/config/test_config.yml: Use named tasks
The unnamed tasks have been reported as issues by new ansible-lint.
2022-02-14 12:57:32 +01:00
Thomas Woerner
dc62744f6a ipaclient install.yml: Use named tasks
The unnamed tasks have been reported as issues by new ansible-lint.
2022-02-14 12:56:08 +01:00
Thomas Woerner
2af7602a8c build-galaxy-release: Fix refs for all doc_fragments in plugins/doc_fragments
The script now fixes the references for all doc_fragments in the
plugins/doc_fragments folder. So far it was only fixing the references
for ipamodule_base_docs.

PR #762 (automount location: add support for delete_continue) added an
other doc_fragment and the references have not been fixes as needed.
2022-02-14 10:51:20 +01:00
Rafael Guterres Jeffman
1b74cf1692 Merge pull request #769 from t-woerner/servicedelegationtarget_list_tests
test_servicedelegationtarget.yml: Added list tests
2022-02-11 08:24:47 -03:00
Rafael Guterres Jeffman
19fc21cd1b hbacrule: Allow clearing members with empty lists.
If a hbacrule member has any value, the only way to clear it is by
creating a task with 'state: absent' and 'action: member' and provide
a list with all the values for that member.

This patch allows the use of '<member>: []' with 'action: hbacrule'
to clear a hbacrule member.

A new test playbook can be found at:

    tests/hbacrule/test_hbacrule_member_empty.yml
2022-02-10 19:08:59 -03:00
Thomas Woerner
804e633f13 test_servicedelegationtarget.yml: Added list tests
List tests, also an empty list test has been added.
2022-02-10 14:00:10 +01:00
Thomas Woerner
ad37bed37b Merge pull request #755 from austlane/master
Fixes `no_log` warning for `ipahost` module
2022-02-09 11:04:23 +01:00
Rafael Guterres Jeffman
b00dc5daa5 Merge pull request #766 from t-woerner/servicedelegationrule
New servicedelegationrule management module
2022-02-08 15:55:43 -03:00
Thomas Woerner
2c278ab39d New servicedelegationrule management module
There is a new servicedelegationrule management module placed in the plugins
folder:

    plugins/modules/ipaservicedelegationrule.py

The servicedelegationrule module allows to ensure presence and absence of
servicedelegationrules and servicedelegationrule members.

Here is the documentation of the module:

    README-servicedelegationrule.md

New example playbooks have been added:

    playbooks/servicedelegationrule/servicedelegationrule-absent.yml
    playbooks/servicedelegationrule/servicedelegationrule-principal-member-absent.yml
    playbooks/servicedelegationrule/servicedelegationrule-principal-member-present.yml
    playbooks/servicedelegationrule/servicedelegationrule-target-member-absent.yml
    playbooks/servicedelegationrule/servicedelegationrule-target-member-present.yml
    playbooks/servicedelegationrule/servicedelegationrule-present.yml

New tests for the module:

    tests/servicedelegationrule/test_servicedelegationrule.yml
    tests/servicedelegationrule/test_servicedelegationrule_client_context.yml
    tests/servicedelegationrule/test_servicedelegationrule_hostprincipal.yml
2022-02-08 14:19:16 +01:00
Rafael Guterres Jeffman
ef2adf54b4 Merge pull request #756 from t-woerner/servicedelegationtarget
New servicedelegationtarget management module
2022-02-07 11:09:10 -03:00
Thomas Woerner
a61c046abe New servicedelegationtarget management module
There is a new servicedelegationtarget management module placed in the plugins
folder:

    plugins/modules/ipaservicedelegationtarget.py

The servicedelegationtarget module allows to ensure presence and absence of
servicedelegationtargets and servicedelegationtarget members.

Here is the documentation of the module:

    README-servicedelegationtarget.md

New example playbooks have been added:

    playbooks/servicedelegationtarget/servicedelegationtarget-absent.yml
    playbooks/servicedelegationtarget/servicedelegationtarget-member-absent.yml
    playbooks/servicedelegationtarget/servicedelegationtarget-member-present.yml
    playbooks/servicedelegationtarget/servicedelegationtarget-present.yml

New tests for the module:

    tests/servicedelegationtarget/test_servicedelegationtarget.yml
    tests/servicedelegationtarget/test_servicedelegationtarget_client_context.yml
    tests/servicedelegationtarget/test_servicedelegationtarget_hostprincipal.yml
2022-02-07 13:00:38 +01:00
Rafael Guterres Jeffman
1fee891aa4 upstream ci: enable ansible-core 2.12 for CentOS 9 Stream.
Enables ansible-core 2.12 for CentOS 9 stream on nightly tests.
2022-02-03 16:05:19 -03:00
Thomas Woerner
1aca0c1304 ansible_freeipa_module: New function servicedelegation_normalize_principals
This function will be used in servicedelegation target and rule modules
to normalize principals given in the tasks. These can be service and host
principals and also aliases.

Note: The use of host principals requires IPA 4.9.0 or later. fail_json
is called if the version is lower.

servicedelegation_normalize_principals contains two embedded fuctions.
One is normalize_principal_name that has been copied from
ipaserver/plugins/servicedelegation.py, the other is the generic
function _check_exists to be able to check if a host or service exists.
2022-02-03 15:40:37 +01:00
Austin
60fd87c567 Fixes no_log warning for ipahost module
Similar to PR 286
This PR explicitly sets `no_log` option for `update_password` attribute to `False`, so that the warning on `no_log` not being set is not issued anymore. Ansible incorrectly issued the warning, as `update_password` does not carry sensitive information.
2022-01-31 13:09:31 -05:00
Rafael Guterres Jeffman
4aab1599bd Merge pull request #753 from t-woerner/group_test_fix_services
group test: Enable ansible_facts, fix service hostname
2022-01-27 10:05:04 -03:00
Thomas Woerner
0c36194038 group test: Enable ansible_facts, fix service hostname
The service hostname needs to be gathered from ansibe_facts as it might
not be "ipaserver". ansible_facts['fqdn'] is now used as the service
hostname, therefore gather_facts had to be turned on.
2022-01-27 11:35:52 +01:00
Thomas Woerner
680cd4c6ee Merge pull request #749 from rjeffman/ipauser_fix_peserved_idempotence_issue
ipauser: Fix idempotence issue when using 'preserved'.
2022-01-26 14:48:33 +01:00
Rafael Guterres Jeffman
401b911171 ipauser: Make 'no user' messages consistent.
When ensuring states 'undeleted', 'enabled', 'disabled', and 'unlocked'
the error messages for an unexistent user were not consistent.

This change changes the message for all states to "No user '%s'."
2022-01-26 08:42:05 -03:00
Rafael Guterres Jeffman
7f61e72a2c ipauser: Fix idempotence issue when using 'preserved'.
When trying to ensure 'state: absent' with 'preserved: yes' in ipauser,
after the first execution the playbook would fail with "user is already
present". Similar idempotence issue would happen when 'state: undelete'
was used.

This PR fixes both issues, and improve tests for the states where user
is preserved, enabled and disabled. The 'find_user' function now uses
IPA API 'user_show' instead of 'user_find' so that only the requested
user is actually returned.
2022-01-25 09:54:56 -03:00
Thomas Woerner
3c3396a7b8 Merge pull request #748 from rjeffman/docs_dnsconfig_example_playbooks
dnsconfig: Add 'action: member' to dnsconfig example playbooks.
2022-01-25 12:44:22 +01:00
Rafael Guterres Jeffman
45f583b1ed dnsconfig: Add 'action: member' to dnsconfig example playbooks.
As of verison 1.6.1 of ansible-freeipa, ipadnsconfig supports
'action: member' to manage DNS forwardes, and requires the use of this
action if 'state: present'.

This patch fixes the playbook examples.
2022-01-24 15:55:18 -03:00
Rafael Guterres Jeffman
2de1dccbf5 Merge pull request #742 from t-woerner/group_fix_services
group: Services are ipapython.kerberos.Principal and case insensitive
2022-01-24 14:56:21 -03:00
Thomas Woerner
a44515c701 Merge pull request #744 from rjeffman/sudorule_fix_deny_sudocmdgroup
sudorule: Fix management of deny_sudocmdgroup.
2022-01-24 17:52:39 +01:00
Thomas Woerner
8cf2e7ef7b group: Services are ipapython.kerberos.Principal and case insensitive
The services returned by group_find are of type
ipapython.kerberos.Principal. Addtionally the services are case
insensitive. Therefore services need to be converted to a lowercase
sting for proper comparison.

test_group.yml has been extended with service tests.
2022-01-24 15:53:40 +01:00
Rafael Guterres Jeffman
ec198d0e09 sudorule: Fix management of deny_sudocmdgroup.
Upstream tests were not testing one path of code related to variable
`deny_sudocmdgroup`, and a regression was added.

This patch fixes a call to the current configuration dictionary, and
add tests so that the code path is executed in the upstream tests.
2022-01-24 11:24:33 -03:00
Thomas Woerner
b162122630 Merge pull request #741 from rjeffman/automount_client_context
automountmap: Add client context test playbook.
2022-01-21 16:12:33 +01:00
Rafael Guterres Jeffman
b89d2b1316 automountmap: Add client context test playbook.
The client context test playbook was missing for ipaautomountmap.
2022-01-21 10:12:30 -03:00
Rafael Guterres Jeffman
1d3eab804d Merge pull request #739 from t-woerner/extend_expire_dates_in_user_tests
User tests: Extend expiration dates for client on server test
2022-01-20 17:41:15 -03:00
Thomas Woerner
d3b8f54d7d User tests: Extend expiration dates for client on server test
The client context on server test is failing with a date that is
expired. The server context on server test is not failing.

Setting an expired date with the command line is possible though.
2022-01-20 16:26:19 +01:00
Thomas Woerner
b7d1a2789b Merge pull request #737 from rjeffman/ipadnsconfig_action_member
dnsconfig: add support for 'action: member'.
2022-01-20 16:22:31 +01:00
Rafael Guterres Jeffman
6bfcfcdc81 dnsconfig: add support for 'action: member'.
This patch adds support for 'action: member' for ipadnsconfig plugin,
impacting management of DNS forwarders setting.

Use of 'state: absent' now requires 'action: member'. With 'state:
present', orwarders can be either defined through 'action: dnsconfig'
or added using 'action: member'.

Tests have been updated to reflec the new behavior.
2022-01-20 12:09:26 -03:00
Thomas Woerner
ebe5671dff Merge pull request #738 from rjeffman/sudorule_fix_idempotence_issues
sudorule: fix idempotence issues and refactor.
2022-01-20 15:57:25 +01:00
Rafael Guterres Jeffman
2266756968 sudorule: fix idempotence issues and refactor.
This change refactors member management for ipasudorule module and
fixes idempotence issues related to case insensitive comparison.
2022-01-20 08:19:41 -03:00
Thomas Woerner
3a0a1a7529 Merge pull request #735 from rjeffman/ipadnsconfig_fix_512
dnsconfig: Fix management of forwarders.
2022-01-20 12:17:00 +01:00
Rafael Guterres Jeffman
65015e63e9 Merge pull request #736 from t-woerner/hostgroup_make_hosts_fqdn
ipahostgroup: Ensure host members are lowercase and FQDN
2022-01-19 14:38:20 -03:00
Rafael Guterres Jeffman
dead467982 dnsconfig: Fix management of forwarders.
If one tries to set a list of forwarders which include an already
existing forwarder, the existing forwarder is removed, and the list
of configured forwarders contain only the new ones.

This patch fixes this behavior by setting a union of the currently
available forwarders and the list of forwarders provided in the
playbook.

Tests were added to ensure this behavior.
2022-01-19 14:36:57 -03:00
Thomas Woerner
ae286f5226 ipahostgroup: Ensure host members are lowercase and FQDN
The host members of ipahostgroup need to be lowercase and FQDN to be
able to do a proper comparison with exising hosts in the hostgroup.

Fixes: #666 (ipahostgroup not idempotent and with error)
2022-01-19 14:25:05 +01:00
Rafael Guterres Jeffman
ea53e34537 Merge pull request #734 from t-woerner/readme_test_roles
README test: Also check role readme files
2022-01-19 09:38:22 -03:00
Thomas Woerner
48b0a13a54 README test: Also check role readme files
The test is now also checking that role README files are mentioned in
the main README.
2022-01-19 13:28:03 +01:00
Rafael Guterres Jeffman
04a8299be6 Merge pull request #733 from t-woerner/ipaclient_get_otp_no_gssapi
ipaclient_get_keytab: Do not use gssapi for kinit_keytab
2022-01-18 10:01:41 -03:00
Thomas Woerner
b0252fb57a ipaclient_get_keytab: Do not use gssapi for kinit_keytab
Due to a change in Ansible to depend on Python 3.8 it is needed to only
use bindings that are provided by Python and Ansible core. gssapi is
therefore not usable any more.

The kinit_keytab function was using gssapi and now has to use the kinit
command insead.
2022-01-18 11:19:20 +01:00
Rafael Guterres Jeffman
78091e2238 Merge pull request #731 from t-woerner/1_6_0_update_README
README.md: Add automount key and map, fix ref to hbacsvcgroup and test
2022-01-17 12:33:35 -03:00
Thomas Woerner
25afcc3491 README.md: Add automount key and map, fix ref to hbacsvcgroup and test
The main REAADME has been fixed to contain information about the
automount key and map modules, the reference to the hbacsvcgroup README
has been fixed and a new test has been added as a github workflow.
2022-01-17 11:14:49 +01:00
Thomas Woerner
6b4fd03bc6 Merge pull request #686 from rjeffman/hbacrule_case_insensitive
hbacrule: Fix member management idempotence issues.
2022-01-13 16:28:43 +01:00
Thomas Woerner
095e6a4155 Merge pull request #684 from rjeffman/iparole_idempotence_issues
iparole: Fix idempotence issues
2022-01-13 16:27:43 +01:00
Rafael Guterres Jeffman
2cb11d44ec Merge pull request #729 from t-woerner/fix_new_ansible_test_findings
ansible-test: Fix new findings
2022-01-13 10:45:44 -03:00
Thomas Woerner
9499a3ed9f ansible-test: Fix new findings
ERROR: Found 6 pylint issue(s) which need to be resolved:
ERROR: plugins/modules/ipaserver_prepare.py:395:4: invalid-name: Variable name "e" doesn't conform to snake_case naming style
ERROR: roles/ipaserver/library/ipaserver_prepare.py:395:4: invalid-name: Variable name "e" doesn't conform to snake_case naming style
ERROR: roles/ipaserver/module_utils/ansible_ipa_server.py:333:12: invalid-name: Variable name "ds" doesn't conform to snake_case naming style
ERROR: roles/ipaserver/module_utils/ansible_ipa_server.py:348:12: invalid-name: Variable name "ds" doesn't conform to snake_case naming style
ERROR: roles/ipaserver/module_utils/ansible_ipa_server.py:361:12: invalid-name: Variable name "ip" doesn't conform to snake_case naming style
ERROR: roles/ipaserver/module_utils/ansible_ipa_server.py:364:12: invalid-name: Variable name "e" doesn't conform to snake_case naming style

e has been replaced with err, ds with _ds, ip with _ip.
2022-01-13 14:28:55 +01:00
Rafael Guterres Jeffman
7632f90edb Merge pull request #724 from t-woerner/enhance_utils_build-galaxy-release_sh
build-galaxy-release.sh: Use build dir, new options, checks, no reset
2022-01-13 10:26:25 -03:00
Rafael Guterres Jeffman
07e9d87e92 iparole: Skip ansible-test verifications for Python 2.6. 2022-01-13 10:20:28 -03:00
Rafael Guterres Jeffman
0cebb3e2a2 hbacrule: Fix member management idempotence issues.
Members of hbacrule must be compared in a case insensitive manner.
This patch fixes comparation of member parameters against existing
members by converting parameters to lowercase.

Also, there were some cases where a change with an empty set of members
was issued to IPA API, leading to a result of 'changed: yes' when
'changed: no' was expected. The fix involved a refactoring of the
member management code.
2022-01-13 10:19:06 -03:00
Rafael Guterres Jeffman
d2bcaa3b81 test playbooks: Add fact to define ipaserver_domain if not set.
Add a task to FreeIPA facts task file to ensure that the variable
'ipaserver_domain' is set.

The value is set form `ansible_facts['fqdn'], if available, or set to
`ipa.test`, otherwise.
2022-01-13 10:04:33 -03:00
Thomas Woerner
37ba14f164 Merge pull request #685 from rjeffman/hbacsvcgroup_case_insensitive
hbacsvcgroup: Fix member management idempotence issues.
2022-01-13 14:00:59 +01:00
Thomas Woerner
9b88207100 Merge pull request #708 from rjeffman/pylint_enable_roles
Enable pylint for ansible-freeipa roles.
2022-01-13 13:42:16 +01:00
Thomas Woerner
9d6a83dce7 Merge pull request #727 from rjeffman/shellcheck_no_docker
pre-commit: Use system shellcheck.
2022-01-13 13:40:20 +01:00
Rafael Guterres Jeffman
b489e2b8a8 Merge pull request #728 from t-woerner/pre_commit_ansible_lint_version_5_3_2
pre-commit: Update ansible-lint version to v5.3.2
2022-01-13 09:06:05 -03:00
Rafael Guterres Jeffman
1d18063497 pre-commit: Use system shellcheck.
The official ShellCheck pre-commit hook uses a docker image, but it
is, sometimes, unavailable. This change will use the system installed
ShellCheck executable and does not depend on the image download.
2022-01-13 08:52:40 -03:00
Thomas Woerner
7548c5afd1 pre-commit: Update ansible-lint version to v5.3.2
This fixes the import error for render_group from rich.console.
2022-01-13 12:46:27 +01:00
Thomas Woerner
27348d8f26 Merge pull request #726 from rjeffman/ghw_change_ansible_lint
Github Workflows: Run ansible-lint without an action.
2022-01-13 11:31:47 +01:00
Rafael Guterres Jeffman
7ba6ae348b Github Workflows: Run ansible-lint without an action.
We used a Github Action to run anisble-lint, but it has not have a
release since 2019, and has not been updated in a year. This action is
showing some issues when evaluating current playbooks.

This PR substitute the action previously used with a shell script
directly define in the workflow job. The ansible-core version was
pinned to the currently available on Fedora 25, 2.11.6.
2022-01-12 19:19:44 -03:00
Rafael Guterres Jeffman
a025e476ea iparole: Add tests to verify if capitalisation is ignored.
The test playbook provided adds some tests to verify if capitalization
of role members does not influence on the module behavior. It also adds
some tests to verify check_mode.
2022-01-12 19:03:33 -03:00
Rafael Guterres Jeffman
a44ffbf3dd iparole: rename function get_lowercase to result_get_value_lowercase
Renamed function and improved its documentation to better explain  its
use and goals.
2022-01-12 19:03:33 -03:00
Rafael Guterres Jeffman
846fdc0698 iparole: Fix idempotence issues with members.
IPA role members users, groups, hostgroups and privilege must be
compared in a case insensitive way, and either are stored in lowercase
or IPA API fixes the value for proper representation.

This patch forces all comparisons of this values to be performed in
lowercase, and also only modify the values if it is really needed.
2022-01-12 19:03:33 -03:00
Rafael Guterres Jeffman
faace4f376 iparole: Ensure host members are lowercase and FQDN.
IPA Role host members should always be lowercase and FQDN. This
patch ensure that hosts are correctly compared and added as role
members.
2022-01-12 19:03:33 -03:00
Rafael Guterres Jeffman
bde3eb8294 IPAAnsibleModule: cache IPA domain.
Some attributes retrieved by the IPA API backend don't change, and are
used more than once, in different places of the code. IPA API domain
is one of these attributes.

This patch adds a cache to the attribute, so there is only one request
for the API, improving access time to the object and alowing multiple
calls with no efficiency penalty.
2022-01-12 19:03:33 -03:00
Rafael Guterres Jeffman
971fcc917a iparole: Case insensitive comparison of service members.
Service members in IPA role objects must be compared ignoring character
capitalization, but are stored in a case preserving manner.

This patch modifies the way service members are handled, creating a map
between a lowercase version of the service parameter and the parameter
itself, and using the map key to compare against existing services. The
mapped value is then added as role member, if necessary.
2022-01-12 19:03:33 -03:00
Rafael Guterres Jeffman
13d7d714d7 iparole: Remove custom code in favor of commom functions.
Removed custom code used to create add/del lists in iparole in favor
of ansible_freeipa_module functions, and custom result_handler, to
reduce code duplication, as these methods have equivalent shared
versions.
2022-01-12 19:03:33 -03:00
Rafael Guterres Jeffman
8a93627079 iparole: Removed unused code.
There was some unused code that was removed.
2022-01-12 19:03:33 -03:00
Rafael Guterres Jeffman
c24ff079d6 Merge pull request #725 from t-woerner/fix_new_ansible_test_findings
ansible-test fixes
2022-01-12 18:59:19 -03:00
Thomas Woerner
4f1a01b85b ansible-test fixes
ERROR: plugins/modules/ipaautomountmap.py:118:30: E203: whitespace before ':'

ERROR: Found 1 compile issue(s) on python 2.6 which need to be resolved:
ERROR: plugins/modules/ipasudorule.py:382:63: SyntaxError: {ensure_fqdn(value.lower(), default_domain) for value in host}
2022-01-12 16:54:52 +01:00
Thomas Woerner
886abee4e2 Merge pull request #674 from rjeffman/sudorule_fix_host_order
sudorule: Create FQDN from single hostnames
2022-01-12 16:10:21 +01:00
Rafael Guterres Jeffman
ce8487e394 pylint: Enable pylint for ansible-freeipa roles.
This patch enables pylint evaluation for ansible-freeipa roles in
both the local script 'utils/lint-check.sh' and in upstream CI.
2022-01-12 12:09:46 -03:00
Rafael Guterres Jeffman
bf5555271d pylint: Fix pylint issues with modules.
Fix pylint warnings raised by enabling linter on ansible-freeipa roles.
2022-01-12 12:09:46 -03:00
Rafael Guterres Jeffman
752fa1087d pylint: Add modules and names that should be ignored by linter.
This change configure pylint to ignore import modules that might not be
availble during development, and ignore names that are relevant in the
FreeIPA domain, even if they don't comply with PEP8.
2022-01-12 12:09:29 -03:00
Thomas Woerner
fe836b538d Merge pull request #721 from rjeffman/ipagroup_fix_member_management
ipagroup: Refactor and fix group member management.
2022-01-12 16:07:38 +01:00
Rafael Guterres Jeffman
746e4c0ffa Merge pull request #723 from t-woerner/sanity-ansible-test
Enable ansible-test in github workflow
2022-01-12 11:59:28 -03:00
Thomas Woerner
8fa29a9522 Enable ansible-test in github workflow
This test is using the galaxy_importer from ansible project. The
configuration file galaxy-importer.cfg is copied from linux-system-roles

    https://github.com/linux-system-roles/auto-maintenance/blob/master/\
    lsr_role2collection/galaxy-importer.cfg

The tests script has extra code to parse the output of the importer to
highlight errors and to exit with a proper error code.

The test can be used locally also with "sh tests/sanity/sanity.sh"

New files:
- .github/workflows/ansible-test.yml
- tests/sanity/galaxy-importer.cfg
- tests/sanity/sanity.sh
2022-01-12 15:42:04 +01:00
Thomas Woerner
de8d724663 build-galaxy-release.sh: Use build dir, new options, checks, no reset
The script is now using a build dir for the creation of the Ansible
Collection. Additionally only files known to the fit repo are pulled in
by default. The new "-a" option is pulling in all files from local repo.
The new -k" option can be used to keep the build dir for verification of
the changes to the files.

The colleciton is placed into the main repo dir and no git reset --hard
is used in the repo to preserve local changes.
2022-01-12 13:46:33 +01:00
Thomas Woerner
b401ba0354 Merge pull request #498 from chr15p/ipaautomountkey
add module to create and manage automount keys
2022-01-12 13:27:32 +01:00
Rafael Guterres Jeffman
dd700d956b Fixed automountkey code review issues.
Fixed several issues found during code review and change
AutomountkeyModule to use IPAAnsibleModule instead of deprecated
FreeIPABaseModule.
2022-01-11 17:52:20 -03:00
chrisp
3ca9982c73 New automount key management module
There is a new automount key module placed in the plugins folder:

    plugins/modules/ipaautomountkey.py

The server module allows to ensure presence and absence of automount
keys. The module requires an existing automount location and map to
place the key within.

Here is the documentation for the module:

    README-automountkey.md

New example playbooks have been added:

    playbooks/automount/automount-key-absent.yaml
    playbooks/automount/automount-key-present.yaml

New tests for the module:

    tests/automount/test_automountkey.yml
2022-01-11 14:12:49 -03:00
Thomas Woerner
6a1f61931d Merge pull request #497 from chr15p/ipaautomountmap
add module to create and manage automount maps
2022-01-11 18:01:25 +01:00
Rafael Guterres Jeffman
e1e8ff5916 Adapt automount to IPAAnsibleModule and add code review modifications. 2022-01-11 09:43:41 -03:00
Rafael Guterres Jeffman
3b08edda50 ipagroup: Refactor and fix group member management.
Currently, when adding an overlapping set of members causes playbook to
fail as the already existing members are added twice.

This patch refactors membership management by removing duplicate logic
and handling all changes to members in a single place. This change
removed code that was causing the execution failures.
2022-01-11 09:27:47 -03:00
chrisp
0d47429000 New automount map management module.
There is a new server management module placed in the plugins folder:

    plugins/modules/ipaautomountmap.py

The server module allows to ensure presence and absence of automount
maps. The module requires an existing automount location to place the
map within. It does not create any automount keys with in the map.

Here is the documentation for the module:

    README-automountmap.md

New example playbooks have been added:

    playbooks/automount/automount-map-absent.yaml
    playbooks/automount/automount-map-present.yaml

New tests for the module:

    tests/automount/test_automountmap.yml
2022-01-05 18:49:27 -03:00
Thomas Woerner
870dfec9df Merge pull request #697 from rjeffman/ci_fix_pytests_ansible_version
upstrem CI: Fix Ansible version in pytest playbooks.
2022-01-05 12:19:30 +01:00
Thomas Woerner
7e62ebd7b4 Merge pull request #696 from rjeffman/ci_centos9_stream
upstream CI:  Add support for CentOS 9 stream.
2022-01-05 12:18:46 +01:00
Thomas Woerner
081d0f658d Merge pull request #706 from rjeffman/ci_ansible_core_2_12
upstream CI: Enable nightly tests using ansible-core 2.12.
2022-01-05 12:17:43 +01:00
Thomas Woerner
d708fc4734 Merge pull request #704 from rjeffman/ansible_doc_test_ansible_2_12
upstream CI: Enable ansible-doc-test for ansible-core 2.12.
2022-01-05 12:17:19 +01:00
Thomas Woerner
4a4700191e Merge pull request #716 from rjeffman/iparole_add_state_renamed
iparole: Add state 'renamed'.
2022-01-05 11:37:03 +01:00
Thomas Woerner
8c88413ac1 Merge pull request #717 from rjeffman/ci_fix_kdc_unavailable
upstream CI: Wait for KDC to be available.
2022-01-04 13:41:25 +01:00
Rafael Guterres Jeffman
30c4748fe2 upstream CI: Wait for KDC to be available.
Sometimes the first test of a batch fails because it fails to grant a
TGT from Kerberos KDC as it is not yet fully working. By waiting until
a TGT can be acquired, these failures will not happen anymore.
2022-01-03 16:26:14 -03:00
Rafael Guterres Jeffman
68f775842d iparole: Add state 'renamed'.
All ansible-freeipa modules which allow object renaming should support
'state: renamed'.

This patch adds suport for the missing state, and fixes cases where a
user could try to rename the object and set its members, which would
fail depending on the operation order.

Fix #566
2021-12-29 11:16:55 -03:00
Rafael Guterres Jeffman
cf7fc949fe sudorule: Create FQDN from single hostnames
Single hostnames can be used for sudorule_add_host and will match fqdn
in IPA internally. Simple host names have to be extended to be FQDN to
be able to compare them for sudorule_host_add and sudorule_host_remove.

Fixes #672
2021-12-29 09:05:10 -03:00
Rafael Guterres Jeffman
e15c716906 upstream CI: Enable ansible-doc-test for ansible-core 2.12. 2021-12-10 11:28:04 -03:00
Rafael Guterres Jeffman
4167982208 upstream CI: Enable nightly tests using ansible-core 2.12.
This patch modifies the Python version used to be the latest available,
and add stages to execute the tests using ansible-core 2.12. As we
use Ubuntu 20.04, Python version 3.8 is avaiable.

Previously, ansible-core 2.12 was not available as it cannot be
installed with Python 3.6, which was the version used.
2021-12-09 22:34:30 -03:00
Rafael Guterres Jeffman
b140f04a9d hbacsvcgroup: Fix member management idempotence issues.
The hbacsvc members of hbacsvcgroup must be compared in a case
insensitive manner. This patch fixes comparation of member parameters
against existing members by converting parameters to lowercase, as it
is how the hbacsvc members are stored for hbacsvcgroups.

Also, there were some cases where a change with an empty set of members
was issued to IPA API, leading to a result of 'changed: yes' when
'changed: no' was expected. The fix involved a refactoring of the
hbacsvcgroup member management code.
2021-12-03 10:02:55 -03:00
Rafael Guterres Jeffman
214b6bba7e ci: Add support for CentOS 9 Stream on upstream CI.
This patch adds support for running upstream tests using Centos-9
stream images. Both pull request and nightly tests are updated.
2021-11-29 12:38:24 -03:00
Rafael Guterres Jeffman
700d2b7335 upstrem CI: Fix Ansible version in pytest playbooks.
When using group_tests, the pytest playbook was not receiving the
Ansible version to use, executing always with the latest available
version.

This patch fixes the behavior by passing the Ansible version to use
for tests to pytest_tests playbook.
2021-11-26 10:17:18 -03:00
215 changed files with 11720 additions and 1605 deletions

View File

@@ -14,6 +14,8 @@ exclude_paths:
kinds:
- playbook: '**/tests/**/test_*.yml'
- playbook: '**/playbooks/**/*.yml'
- tasks: '**/tasks_*.yml'
- tasks: '**/env_*.yml'
parseable: true

17
.github/workflows/ansible-test.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
---
name: ansible-test sanity
on:
- push
- pull_request
jobs:
ansible_test:
name: Verify ansible-test sanity
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
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

@@ -12,9 +12,11 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Run ansible-doc-test
- name: Install Ansible 2.9
run: |
python -m pip install "ansible < 2.10"
- name: Run ansible-doc-test
run: |
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
check_docs_2_11:
@@ -25,9 +27,27 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Run ansible-doc-test
- name: Install Ansible 2.11
run: |
python -m pip install "ansible-core >=2.11,<2.12"
- name: Run ansible-doc-test
run: |
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
check_docs_2_12:
name: Check Ansible Documentation with ansible-core 2.12.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
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: |
python -m pip install "ansible-core >=2.12,<2.13"
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
check_docs_latest:
@@ -38,7 +58,9 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Run ansible-doc-test
- name: Install Ansible-latest
run: |
python -m pip install ansible
- name: Run ansible-doc-test
run: |
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins

View File

@@ -13,15 +13,9 @@ jobs:
with:
python-version: "3.x"
- name: Run ansible-lint
uses: ansible/ansible-lint-action@master
with:
targets: |
tests/*.yml
tests/*/*.yml
tests/*/*/*.yml
playbooks/*.yml
playbooks/*/*.yml
roles/*/*/*.yml
run: |
pip install ansible-core==2.11.6 ansible-lint
find playbooks roles tests -name '*.yml' ! -name "env_*" ! -name "tasks_*" -exec ansible-lint --force-color {} \+
env:
ANSIBLE_MODULE_UTILS: plugins/module_utils
ANSIBLE_LIBRARY: plugins/modules
@@ -74,8 +68,8 @@ jobs:
python-version: "3.x"
- name: Run pylint
run: |
pip install pylint==2.10.2
pylint plugins --disable=import-error
pip install pylint==2.12.2
pylint plugins roles --disable=import-error
shellcheck:
name: Shellcheck

16
.github/workflows/readme.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
---
name: readme test
on:
- push
- pull_request
jobs:
ansible_test:
name: Verify readme
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run readme test
run: |
error=0
for i in roles/ipa*/README.md README-*.md; do grep -q $i README.md && echo "OK: $i" || { echo -e "\033[31;1mERROR: ${i} missing\033[0m"; error=1; } done
exit $error

View File

@@ -1,7 +1,7 @@
---
repos:
- repo: https://github.com/ansible/ansible-lint.git
rev: v5.1.2
rev: v5.3.2
hooks:
- id: ansible-lint
always_run: false
@@ -24,7 +24,7 @@ repos:
hooks:
- id: pydocstyle
- repo: https://github.com/pycqa/pylint
rev: v2.10.2
rev: v2.12.2
hooks:
- id: pylint
args:
@@ -38,8 +38,10 @@ repos:
entry: utils/ansible-doc-test
# args: ['-v', 'roles', 'plugins']
files: ^.*.py$
- repo: https://github.com/koalaman/shellcheck-precommit
rev: v0.8.0
- repo: local
hooks:
- id: shellcheck
args: ["--severity=warning"] # Only show errors and warnings
name: ShellCheck
language: system
entry: shellcheck
files: \.sh$

112
README-automountkey.md Normal file
View File

@@ -0,0 +1,112 @@
Automountkey module
=====================
Description
-----------
The automountkey module allows management of keys within an automount map.
It is desgined to follow the IPA api as closely as possible while ensuring ease of use.
Features
--------
* Automount key management
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipaautomountkey module.
Requirements
------------
**Controller**
* Ansible version: 2.8+
**Node**
* Supported FreeIPA version (see above)
Usage
=====
Example inventory file
```ini
[ipaserver]
ipaserver.test.local
```
Example playbook to ensure presence of an automount key:
```yaml
---
- name: Playbook to manage automount key
hosts: ipaserver
tasks:
- name: ensure automount key TestKey is present
ipaautomountkey:
ipaadmin_password: SomeADMINpassword
location: TestLocation
mapname: TestMap
key: TestKey
info: 192.168.122.1:/exports
state: present
```
Example playbook to rename an automount map:
```yaml
---
- name: Playbook to add an automount map
hosts: ipaserver
tasks:
- name: ensure aumount key TestKey is renamed to NewKeyName
ipaautomountkey:
ipaadmin_password: password01
automountlocationcn: TestLocation
automountmapname: TestMap
automountkey: TestKey
newname: NewKeyName
state: renamed
```
Example playbook to ensure an automount key is absent:
```yaml
---
- name: Playbook to manage an automount key
hosts: ipaserver
tasks:
- name: ensure automount key TestKey is absent
ipaautomountkey:
ipaadmin_password: SomeADMINpassword
location: TestLocation
mapname: TestMap
key: TestKey
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
`location` \| `automountlocationcn` \| `automountlocation` | Location name. | yes
`mapname` \| `map` \| `automountmapname` \| `automountmap` | Map the key belongs to | yes
`key` \| `name` \| `automountkey` | Automount key to manage | yes
`rename` \| `new_name` \| `newautomountkey` | the name to change the key to if state is `renamed` | yes when state is `renamed`
`info` \| `information` \| `automountinformation` | Mount information for the key | yes when state is `present`
`state` | The state to ensure. It can be one of `present`, `absent` or `renamed`, default: `present`. | no
Authors
=======
Chris Procter

96
README-automountmap.md Normal file
View File

@@ -0,0 +1,96 @@
Automountmap module
=====================
Description
-----------
The automountmap module allows the addition and removal of maps within automount locations.
It is desgined to follow the IPA api as closely as possible while ensuring ease of use.
Features
--------
* Automount map management
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipaautomountmap module.
Requirements
------------
**Controller**
* Ansible version: 2.8+
**Node**
* Supported FreeIPA version (see above)
Usage
=====
Example inventory file
```ini
[ipaserver]
ipaserver.test.local
```
Example playbook to ensure presence of an automount map:
```yaml
---
- name: Playbook to add an automount map
hosts: ipaserver
become: no
tasks:
- name: ensure map named auto.DMZ in location DMZ is created
ipaautomountmap:
ipaadmin_password: SomeADMINpassword
name: auto.DMZ
location: DMZ
desc: "this is a map for servers in the DMZ"
```
Example playbook to ensure auto.DMZi is absent:
```yaml
---
- name: Playbook to remove an automount map
hosts: ipaserver
become: no
tasks:
- name: ensure map auto.DMZ has been removed
ipaautomountmap:
ipaadmin_password: SomeADMINpassword
name: auto.DMZ
location: DMZ
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
`name` \| `mapname` \| `map` \| `automountmapname` | Name of the map to manage | yes
`location` \| `automountlocation` \| `automountlocationcn` | Location name. | yes
`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

View File

@@ -71,6 +71,7 @@ Example playbook to ensure a global forwarder, with a custom port, is absent:
forwarders:
- ip_address: 2001:4860:4860::8888
port: 53
action: member
state: absent
```
@@ -128,9 +129,10 @@ Variable | Description | Required
`forwarders` | The list of forwarders dicts. Each `forwarders` dict entry has:| no
&nbsp; | `ip_address` - The IPv4 or IPv6 address of the DNS server. | yes
&nbsp; | `port` - The custom port that should be used on this server. | no
`forward_policy` | The global forwarding policy. It can be one of `only`, `first`, or `none`. | no
`forward_policy` \| `forwardpolicy` | The global forwarding policy. It can be one of `only`, `first`, or `none`. | no
`allow_sync_ptr` | Allow synchronization of forward (A, AAAA) and reverse (PTR) records (bool). | yes
`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | yes
`action` | Work on dnsconfig or member level. It can be one of `member` or `dnsconfig` and defaults to `dnsconfig`. Only `forwarders` can be managed with `action: member`. | no
`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. `absent` can only be used with `action: member` and `forwarders`. | yes
Authors

View File

@@ -110,7 +110,7 @@ Variable | Description | Required
`forwarders` \| `idnsforwarders` | Per-zone forwarders. A custom port can be specified for each forwarder. Options | no
&nbsp; | `ip_address`: The forwarder IP address. | yes
&nbsp; | `port`: The forwarder IP port. | no
`forwardpolicy` \| `idnsforwardpolicy` | Per-zone conditional forwarding policy. Possible values are `only`, `first`, `none`. Set to "none" to disable forwarding to global forwarder for this zone. In that case, conditional zone forwarders are disregarded. | no
`forwardpolicy` \| `idnsforwardpolicy` \| `forward_policy` | Per-zone conditional forwarding policy. Possible values are `only`, `first`, `none`. Set to "none" to disable forwarding to global forwarder for this zone. In that case, conditional zone forwarders are disregarded. | no
`skip_overlap_check` | Force DNS zone creation even if it will overlap with an existing zone. Defaults to False. | no
`permission` | Allow DNS Forward Zone to be managed. (bool) | no
`action` | Work on group or member level. It can be on of `member` or `dnsforwardzone` and defaults to `dnsforwardzone`. | no

View File

@@ -100,7 +100,7 @@ Example playbook to add group members to a group:
become: true
tasks:
# Add group members sysops and appops to group sysops
# Add group members sysops and appops to group ops
- ipagroup:
ipaadmin_password: SomeADMINpassword
name: ops
@@ -166,6 +166,7 @@ Variable | Description | Required
`membermanager_user` | List of member manager users assigned to this group. Only usable with IPA versions 4.8.4 and up. | no
`membermanager_group` | List of member manager groups assigned to this group. Only usable with IPA versions 4.8.4 and up. | no
`externalmember` \| `ipaexternalmember` \| `external_member`| List of members of a trusted domain in DOM\\name or name@domain form. | no
`idoverrideuser` | List of user ID overrides to manage. Only usable with IPA versions 4.8.7 and up.| no
`action` | Work on group or member level. It can be on of `member` or `group` and defaults to `group`. | no
`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | yes

196
README-idrange.md Normal file
View File

@@ -0,0 +1,196 @@
Idrange module
============
Description
-----------
The idrange module allows the management of ID ranges.
In general it is not necessary to modify or delete ID ranges. If there is no other way to achieve a certain configuration than to modify or delete an ID range it should be done with great care. Because UIDs are stored in the file system and are used for access control it might be possible that users are allowed to access files of other users if an ID range got deleted and reused for a different domain.
Use cases
---------
* Add an ID range from a transitively trusted domain
If the trusted domain (A) trusts another domain (B) as well and this trust is transitive 'ipa trust-add domain-A' will only create a range for domain A. The ID range for domain B must be added manually.
* Add an additional ID range for the local domain
If the ID range of the local domain is exhausted, i.e. no new IDs can be assigned to Posix users or groups by the DNA plugin, a new range has to be created to allow new users and groups to be added. (Currently there is no connection between this range CLI and the DNA plugin, but a future version might be able to modify the configuration of the DNS plugin as well).
Features
--------
* ID Range management
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipaidrange module.
Requirements
------------
**Controller**
* Ansible version: 2.8+
**Node**
* Supported FreeIPA version (see above)
Usage
=====
Example inventory file
```ini
[ipaserver]
ipaserver.test.local
```
Example playbook to ensure a local domain idrange is present:
```yaml
---
- name: Playbook to manage IPA idrange.
hosts: ipaserver
become: no
tasks:
- name: Ensure an ID Range for the local domain is present.
ipaidrange:
ipaadmin_password: SomeADMINpassword
name: local_domain_id_range
base_id: 150000
range_size: 200000
```
Example playbook to ensure a local domain idrange is present, with RID and secondary RID base values:
```yaml
---
- name: Playbook to manage IPA idrange.
hosts: ipaserver
become: no
tasks:
- name: Ensure local idrange is present
ipaidrange:
ipaadmin_password: SomeADMINpassword
name: local_domain_id_range
base_id: 150000000
range_size: 200000
rid_base: 1000000
secondary_rid_base: 200000000
```
Example playbook to ensure an AD-trust idrange is present, with range type 'trust-ad' and using domain SID:
```yaml
---
- name: Playbook to manage IPA idrange.
hosts: ipaserver
become: no
tasks:
- name: Ensure AD-trust idrange is present
ipaidrange:
ipaadmin_password: SomeADMINpassword
name: ad_id_range
base_id: 150000000
range_size: 200000
idrange_type: ipa-ad-trust
dom_sid: S-1-5-21-2870384104-3340008087-3140804251
```
Example playbook to ensure an AD-trust idrange is present, with range type 'trust-ad-posix' and using domain SID:
```yaml
---
- name: Playbook to manage IPA idrange.
hosts: ipaserver
become: no
tasks:
- name: Ensure AD-trust idrange is present
ipaidrange:
name: ad_posix_id_range
base_id: 150000000
range_size: 200000
idrange_type: ipa-ad-trust-posix
dom_name: ad.ipa.test
```
Example playbook to ensure an AD-trust idrange has auto creation of groups set to 'hybrid':
```yaml
---
- name: Playbook to manage IPA idrange.
hosts: ipaserver
become: no
tasks:
- name: Modify AD-trust idrange 'auto_private_groups'
ipaidrange:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ad_id_range
auto_private_groups: "hybrid"
```
Example playbook to make sure an idrange is absent:
```yaml
---
- name: Playbook to manage IPA idrange.
hosts: ipaserver
become: no
tasks:
- name: Ensure ID range 'ad_id_range' is absent.
ipaidrange:
ipaadmin_password: SomeADMINpassword
name: ad_id_range
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 yes. (bool) | no
`name` \| `cn` | The list of idrange name strings. | yes
`base_id` \| `ipabaseid` | First Posix ID of the range. (int) | yes, if `state: present`
`range_size` \| `ipaidrangesize` | Number of IDs in the range. (int) | yes, if `state: present`
`rid_base` \| `ipabaserid` | First RID of the corresponding RID range. (int) | no
`secondary_rid_base` \| `ipasecondarybaserid` | First RID of the secondary RID range. (int) | no
`dom_sid` \| `ipanttrusteddomainsid` | Domain SID of the trusted domain. | no
`idrange_type` \| `iparangetype` | ID range type, one of `ipa-ad-trust`, `ipa-ad-trust-posix`, `ipa-local`. Only valid if idrange does not exist. | no
`dom_name` \| `ipanttrusteddomainname` | Name of the trusted domain. Can only be used when `ipaapi_context: server`. | no
`auto_private_groups` \| `ipaautoprivategroups` | Auto creation of private groups, one of `true`, `false`, `hybrid`. | no
`delete_continue` \| `continue` | Continuous mode: don't stop on errors. Valid only if `state` is `absent`. Default: `no` (bool) | no
`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no
Notes
=====
DNA plugin in 389-ds will allocate IDs based on the ranges configured for the local domain. Currently the DNA plugin *cannot* be reconfigured itself based on the local ranges set via this family of commands.
Manual configuration change has to be done in the DNA plugin configuration for the new local range. Specifically, The dnaNextRange attribute of 'cn=Posix IDs,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config' has to be modified to match the new range.
Authors
=======
Rafael Guterres Jeffman

View File

@@ -293,8 +293,8 @@ Variable | Description | Required
`ipaapi_ldap_cache` | Use LDAP cache for IPA connection. The bool setting defaults to yes. (bool) | no
`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`. | no
`auth_ind` \| `krbprincipalauthind` | Defines an allow list for Authentication Indicators. It can be any of `otp`, `radius`, `pkinit`, or `hardened`. | 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
`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

@@ -0,0 +1,172 @@
Servicedelegationrule module
============
Description
-----------
The servicedelegationrule module allows to ensure presence and absence of servicedelegationrules and servicedelegationrule members.
Features
--------
* Servicedelegationrule management
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipaservicedelegationrule module.
Host princpals are only usable with IPA versions 4.9.0 and up.
Requirements
------------
**Controller**
* Ansible version: 2.8+
**Node**
* Supported FreeIPA version (see above)
Usage
=====
Example inventory file
```ini
[ipaserver]
ipaserver.test.local
```
Example playbook to make sure servicedelegationrule delegation-rule is present:
```yaml
---
- name: Playbook to manage IPA servicedelegationrule
hosts: ipaserver
become: no
tasks:
- name: Ensure servicedelegationrule delegation-rule is present
ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: delegation-rule
```
Example playbook to make sure servicedelegationrule delegation-rule member principal test/example.com is present:
```yaml
---
- name: Playbook to manage IPA servicedelegationrule
hosts: ipaserver
become: no
tasks:
- name: Ensure servicedelegationrule delegation-rule member principal test/example.com is present
ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: delegation-rule
principal: test/example.com
action: member
```
Example playbook to make sure servicedelegationrule delegation-rule member principal test/example.com is absent:
```yaml
---
- name: Playbook to manage IPA servicedelegationrule
hosts: ipaserver
become: no
tasks:
- name: Ensure servicedelegationrule delegation-rule member principal test/example.com is absent
ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: delegation-rule
principal: test/example.com
action: member
state: absent
state: absent
```
Example playbook to make sure servicedelegationrule delegation-rule member target delegation-target is present:
```yaml
---
- name: Playbook to manage IPA servicedelegationrule
hosts: ipaserver
become: no
tasks:
- name: Ensure servicedelegationrule delegation-rule member target delegation-target is present
ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: delegation-rule
target: delegation-target
action: member
```
Example playbook to make sure servicedelegationrule delegation-rule member target delegation-target is absent:
```yaml
---
- name: Playbook to manage IPA servicedelegationrule
hosts: ipaserver
become: no
tasks:
- name: Ensure servicedelegationrule delegation-rule member target delegation-target is absent
ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: delegation-rule
target: delegation-target
action: member
state: absent
state: absent
```
Example playbook to make sure servicedelegationrule delegation-rule is absent:
```yaml
---
- name: Playbook to manage IPA servicedelegationrule
hosts: ipaserver
become: no
tasks:
- name: Ensure servicedelegationrule delegation-rule is absent
ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: delegation-rule
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 yes. (bool) | no
`name` \| `cn` | The list of servicedelegationrule name strings. | yes
`principal` | The list of principals. A principal can be of the format: fqdn, fqdn@REALM, service/fqdn, service/fqdn@REALM, host/fqdn, host/fqdn@REALM, alias$, alias$@REALM, where fqdn and fqdn@REALM are host principals and the same as host/fqdn and host/fqdn@REALM. Host princpals are only usable with IPA versions 4.9.0 and up. | no
`target` \| `servicedelegationtarget` | The list of service delegation targets. | no
`action` | Work on servicedelegationrule or member level. It can be on of `member` or `servicedelegationrule` and defaults to `servicedelegationrule`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no
Authors
=======
Thomas Woerner

View File

@@ -0,0 +1,133 @@
Servicedelegationtarget module
============
Description
-----------
The servicedelegationtarget module allows to ensure presence and absence of servicedelegationtargets and servicedelegationtarget members.
Features
--------
* Servicedelegationtarget management
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipaservicedelegationtarget module.
Host princpals are only usable with IPA versions 4.9.0 and up.
Requirements
------------
**Controller**
* Ansible version: 2.8+
**Node**
* Supported FreeIPA version (see above)
Usage
=====
Example inventory file
```ini
[ipaserver]
ipaserver.test.local
```
Example playbook to make sure servicedelegationtarget delegation-target is present:
```yaml
---
- name: Playbook to manage IPA servicedelegationtarget
hosts: ipaserver
become: no
tasks:
- name: Ensure servicedelegationtarget delegation-target is present
ipaservicedelegationtarget:
ipaadmin_password: SomeADMINpassword
name: delegation-target
```
Example playbook to make sure servicedelegationtarget delegation-target member principal test/example.com is present:
```yaml
---
- name: Playbook to manage IPA servicedelegationtarget
hosts: ipaserver
become: no
tasks:
- name: Ensure servicedelegationtarget delegation-target member principal test/example.com is present
ipaservicedelegationtarget:
ipaadmin_password: SomeADMINpassword
name: delegation-target
principal: test/example.com
action: member
```
Example playbook to make sure servicedelegationtarget delegation-target member principal test/example.com is absent:
```yaml
---
- name: Playbook to manage IPA servicedelegationtarget
hosts: ipaserver
become: no
tasks:
- name: Ensure servicedelegationtarget delegation-target member principal test/example.com is absent
ipaservicedelegationtarget:
ipaadmin_password: SomeADMINpassword
name: delegation-target
principal: test/example.com
action: member
state: absent
state: absent
```
Example playbook to make sure servicedelegationtarget delegation-target is absent:
```yaml
---
- name: Playbook to manage IPA servicedelegationtarget
hosts: ipaserver
become: no
tasks:
- name: Ensure servicedelegationtarget delegation-target is absent
ipaservicedelegationtarget:
ipaadmin_password: SomeADMINpassword
name: delegation-target
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 yes. (bool) | no
`name` \| `cn` | The list of servicedelegationtarget name strings. | yes
`principal` | The list of principals. A principal can be of the format: fqdn, fqdn@REALM, service/fqdn, service/fqdn@REALM, host/fqdn, host/fqdn@REALM, alias$, alias$@REALM, where fqdn and fqdn@REALM are host principals and the same as host/fqdn and host/fqdn@REALM. Host princpals are only usable with IPA versions 4.9.0 and up. | no
`action` | Work on servicedelegationtarget or member level. It can be on of `member` or `servicedelegationtarget` and defaults to `servicedelegationtarget`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no
Authors
=======
Thomas Woerner

View File

@@ -105,6 +105,7 @@ Variable | Description | Required
`password` | Active Directory domain administrator's password string. | no
`server` | Domain controller for the Active Directory domain string. | no
`trust_secret` | Shared secret for the trust string. | no
`trust_type` | Trust type. Currently, only 'ad' for Active Directory is supported. | no
`base_id` | First posix id for the trusted domain integer. | no
`range_size` | Size of the ID range reserved for the trusted domain integer. | no
`range_type` | Type of trusted domain ID range, It can be one of `ipa-ad-trust` or `ipa-ad-trust-posix`and defaults to `ipa-ad-trust`. | no

View File

@@ -12,8 +12,11 @@ Features
* One-time-password (OTP) support for client installation
* Repair mode for clients
* Backup and restore, also to and from controller
* Smartcard setup for servers and clients
* Modules for automembership rule management
* Modules for automount key management
* Modules for automount location management
* Modules for automount map management
* Modules for config management
* Modules for delegation management
* Modules for dns config management
@@ -26,6 +29,7 @@ Features
* Modules for hbacsvcgroup management
* Modules for host management
* Modules for hostgroup management
* Modules for idrange management
* Modules for location management
* Modules for permission management
* Modules for privilege management
@@ -34,6 +38,8 @@ Features
* Modules for self service management
* Modules for server management
* Modules for service management
* Modules for service delegation rule management
* Modules for service delegation target management
* Modules for sudocmd management
* Modules for sudocmdgroup management
* Modules for sudorule management
@@ -63,7 +69,6 @@ Requirements
**Controller**
* Ansible version: 2.8+ (ansible-freeipa is an Ansible Collection)
* /usr/bin/kinit is required on the controller if a one time password (OTP) is used
* python3-gssapi is required on the controller if a one time password (OTP) is used with keytab to install the client.
**Node**
* Supported FreeIPA version (see above)
@@ -283,7 +288,8 @@ ipaserver_domain=test.local
ipaserver_realm=TEST.LOCAL
```
For enhanced security it is possible to use a auto-generated one-time-password (OTP). This will be generated on the controller using the (first) server. It is needed to have the python-gssapi bindings installed on the controller for this.
For enhanced security it is possible to use a auto-generated one-time-password (OTP). This will be generated on the controller using the (first) server.
To enable the generation of the one-time-password:
```yaml
[ipaclients:vars]
@@ -420,12 +426,16 @@ Roles
* [Replica](roles/ipareplica/README.md)
* [Client](roles/ipaclient/README.md)
* [Backup](roles/ipabackup/README.md)
* [SmartCard server](roles/ipasmartcard_server/README.md)
* [SmartCard client](roles/ipasmartcard_client/README.md)
Modules in plugin/modules
=========================
* [ipaautomember](README-automember.md)
* [ipaautomountkey](README-automountkey.md)
* [ipaautomountlocation](README-automountlocation.md)
* [ipaautomountmap](README-automountmap.md)
* [ipaconfig](README-config.md)
* [ipadelegation](README-delegation.md)
* [ipadnsconfig](README-dnsconfig.md)
@@ -435,9 +445,10 @@ Modules in plugin/modules
* [ipagroup](README-group.md)
* [ipahbacrule](README-hbacrule.md)
* [ipahbacsvc](README-hbacsvc.md)
* [ipahbacsvcgroup](README-hbacsvc.md)
* [ipahbacsvcgroup](README-hbacsvcgroup.md)
* [ipahost](README-host.md)
* [ipahostgroup](README-hostgroup.md)
* [idrange](README-idrange.md)
* [ipalocation](README-location.md)
* [ipapermission](README-permission.md)
* [ipaprivilege](README-privilege.md)
@@ -446,6 +457,8 @@ Modules in plugin/modules
* [ipaselfservice](README-selfservice.md)
* [ipaserver](README-server.md)
* [ipaservice](README-service.md)
* [ipaservicedelegationrule](README-servicedelegationrule.md)
* [ipaservicedelegationtarget](README-servicedelegationtarget.md)
* [ipasudocmd](README-sudocmd.md)
* [ipasudocmdgroup](README-sudocmdgroup.md)
* [ipasudorule](README-sudorule.md)

View File

@@ -0,0 +1,30 @@
FROM quay.io/centos/centos:stream8
ENV container=docker
RUN rm -fv /var/cache/dnf/metadata_lock.pid; \
dnf makecache; \
dnf --assumeyes install \
/usr/bin/python3 \
/usr/bin/python3-config \
/usr/bin/dnf-3 \
sudo \
bash \
systemd \
procps-ng \
iproute && \
dnf clean all; \
(cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*; \
rm -rf /var/cache/dnf/;
STOPSIGNAL RTMIN+3
VOLUME ["/sys/fs/cgroup"]
CMD ["/usr/sbin/init"]

View File

@@ -0,0 +1,19 @@
---
driver:
name: docker
platforms:
- name: c8s-build
image: "quay.io/centos/centos:stream8"
dockerfile: Dockerfile
hostname: ipaserver.test.local
dns_servers:
- 8.8.8.8
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
command: /usr/sbin/init
privileged: true
provisioner:
name: ansible
playbooks:
prepare: ../resources/playbooks/prepare-build.yml
prerun: false

View File

@@ -2,8 +2,8 @@
driver:
name: docker
platforms:
- name: centos-8
image: quay.io/ansible-freeipa/upstream-tests:centos-8
- name: c8s
image: quay.io/ansible-freeipa/upstream-tests:c8s
pre_build_image: true
hostname: ipaserver.test.local
dns_servers:
@@ -16,3 +16,4 @@ provisioner:
name: ansible
playbooks:
prepare: ../resources/playbooks/prepare.yml
prerun: false

View File

@@ -5,7 +5,6 @@ RUN rm -fv /var/cache/dnf/metadata_lock.pid; \
dnf makecache; \
dnf --assumeyes install \
/usr/bin/python3 \
/usr/bin/python3-config \
/usr/bin/dnf-3 \
sudo \
bash \

View File

@@ -2,7 +2,7 @@
driver:
name: docker
platforms:
- name: centos-9-build
- name: c9s-build
image: "quay.io/centos/centos:stream9"
dockerfile: Dockerfile
hostname: ipaserver.test.local
@@ -16,3 +16,4 @@ provisioner:
name: ansible
playbooks:
prepare: ../resources/playbooks/prepare-build.yml
prerun: false

19
molecule/c9s/molecule.yml Normal file
View File

@@ -0,0 +1,19 @@
---
driver:
name: docker
platforms:
- name: c9s
image: quay.io/ansible-freeipa/upstream-tests:c9s
pre_build_image: true
hostname: ipaserver.test.local
dns_servers:
- 127.0.0.1
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
command: /usr/sbin/init
privileged: true
provisioner:
name: ansible
playbooks:
prepare: ../resources/playbooks/prepare.yml
prerun: false

View File

@@ -16,3 +16,4 @@ provisioner:
name: ansible
playbooks:
prepare: ../resources/playbooks/prepare-build.yml
prerun: false

View File

@@ -16,3 +16,4 @@ provisioner:
name: ansible
playbooks:
prepare: ../resources/playbooks/prepare.yml
prerun: false

View File

@@ -1 +1 @@
centos-8
fedora-latest

View File

@@ -16,3 +16,4 @@ provisioner:
name: ansible
playbooks:
prepare: ../resources/playbooks/prepare-build.yml
prerun: false

View File

@@ -16,3 +16,4 @@ provisioner:
name: ansible
playbooks:
prepare: ../resources/playbooks/prepare.yml
prerun: false

View File

@@ -0,0 +1,30 @@
FROM fedora:rawhide
ENV container=docker
RUN rm -fv /var/cache/dnf/metadata_lock.pid; \
dnf makecache; \
dnf --assumeyes install \
/usr/bin/python3 \
/usr/bin/python3-config \
/usr/bin/dnf-3 \
sudo \
bash \
systemd \
procps-ng \
iproute && \
dnf clean all; \
(cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*; \
rm -rf /var/cache/dnf/;
STOPSIGNAL RTMIN+3
VOLUME ["/sys/fs/cgroup"]
CMD ["/usr/sbin/init"]

View File

@@ -2,9 +2,9 @@
driver:
name: docker
platforms:
- name: centos-8-build
image: "centos:centos8"
pre_build_image: true
- name: fedora-rawhide-build
image: "fedora:rawhide"
dockerfile: Dockerfile
hostname: ipaserver.test.local
dns_servers:
- 8.8.8.8
@@ -16,3 +16,4 @@ provisioner:
name: ansible
playbooks:
prepare: ../resources/playbooks/prepare-build.yml
prerun: false

View File

@@ -0,0 +1,19 @@
---
driver:
name: docker
platforms:
- name: fedora-rawhide
image: quay.io/ansible-freeipa/upstream-tests:fedora-rawhide
pre_build_image: true
hostname: ipaserver.test.local
dns_servers:
- 127.0.0.1
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
command: /usr/sbin/init
privileged: true
provisioner:
name: ansible
playbooks:
prepare: ../resources/playbooks/prepare.yml
prerun: false

View File

@@ -25,3 +25,24 @@
ansible.builtin.service:
name: ipa
state: started
- name: Wait for krb5dkc to be running
ansible.builtin.service_facts:
no_log: True
register: result
until: "'krb5kdc.service' in result.ansible_facts.services and \
result.ansible_facts.services['krb5kdc.service'].state == 'running'"
retries: 30
delay: 5
- name: Check if TGT is available for admin.
ansible.builtin.shell:
cmd: echo SomeADMINpassword | kinit -c ansible_freeipa_cache admin
register: result
until: not result.failed
retries: 30
delay: 5
- name: Cleanup TGT.
ansible.builtin.shell:
cmd: kdestroy -c ansible_freeipa_cache -A

View File

@@ -0,0 +1,12 @@
---
- name: Automount map absent example
hosts: ipaserver
become: no
tasks:
- name: ensure map TestMap is absent
ipaautomountmap:
ipaadmin_password: SomeADMINpassword
name: TestMap
location: TestLocation
state: absent

View File

@@ -0,0 +1,12 @@
---
- name: Automount map present example
hosts: ipaserver
become: no
tasks:
- name: ensure map TestMap is present
ipaautomountmap:
ipaadmin_password: SomeADMINpassword
name: TestMap
location: TestLocation
desc: "this is a test map"

View File

@@ -0,0 +1,13 @@
---
- name: Playbook to manage an automout key
hosts: ipaserver
tasks:
- name: Ensure autmount key is present
ipaautomountkey:
ipaadmin_password: SomeADMINpassword
location: TestLocation
mapname: TestMap
key: TestKey
info: 192.168.122.1:/exports
state: present

View File

@@ -0,0 +1,13 @@
---
- name: Playbook to manage an automount key
hosts: ipaserver
tasks:
- name: Ensure aumount key TestKey is renamed to NewKeyName
ipaautomountkey:
ipaadmin_password: SomeADMINpassword
automountlocationcn: TestLocation
automountmapname: TestMap
automountkey: TestKey
newname: NewKeyName
state: renamed

View File

@@ -0,0 +1,12 @@
---
- name: Playbook to manage an automount key
hosts: ipaserver
tasks:
- name: Ensure autmount key is present
ipaautomountkey:
ipaadmin_password: SomeADMINpassword
location: TestLocation
mapname: TestMap
key: TestKey
state: absent

View File

@@ -6,4 +6,5 @@
tasks:
- name: Disable global forwarders.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
forward_policy: none

View File

@@ -6,4 +6,5 @@
tasks:
- name: Disallow reverse record synchronization.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
allow_sync_ptr: no

View File

@@ -4,10 +4,12 @@
become: true
tasks:
- name: Set dnsconfig.
- name: Set dnsconfig forwarders.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
forwarders:
- ip_address: 8.8.4.4
- ip_address: 2001:4860:4860::8888
port: 53
action: member
state: absent

View File

@@ -0,0 +1,14 @@
---
- name: Playbook to handle global DNS configuration
hosts: ipaserver
become: true
tasks:
- name: Set dnsconfig forwarders.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
forwarders:
- ip_address: 8.8.4.4
- ip_address: 2001:4860:4860::8888
port: 53
action: member

View File

@@ -6,6 +6,7 @@
tasks:
- name: Set dnsconfig.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
forwarders:
- ip_address: 8.8.4.4
- ip_address: 2001:4860:4860::8888

View File

@@ -7,6 +7,7 @@
tasks:
- name: Ensure that 'host04' has CNAME, with cname_hostname, is absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: example.com
name: host04
cname_hostname: host04.example.com

View File

@@ -7,6 +7,7 @@
tasks:
- name: Ensure that 'host04' has CNAME, with cname_hostname, is present
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: example.com
name: host04
cname_hostname: host04.example.com

View File

@@ -0,0 +1,11 @@
---
- name: Idrange absent example
hosts: ipaserver
become: no
tasks:
- name: Ensure idrange is absent
ipaidrange:
ipaadmin_password: SomeADMINpassword
name: id_range
state: absent

View File

@@ -0,0 +1,15 @@
---
- name: Playbook to manage idrange
hosts: ipaserver
become: no
tasks:
- name: Ensure AD-trust idrange is present
ipaidrange:
name: id_range
base_id: 150000000
range_size: 200000
rid_base: 1000000
idrange_type: ipa-ad-trust-posix
dom_name: ad.ipa.test
auto_private_groups: "false"

View File

@@ -0,0 +1,16 @@
---
- name: Playbook to manage idrange
hosts: ipaserver
become: no
tasks:
- name: Ensure AD-trust idrange is present
ipaidrange:
ipaadmin_password: SomeADMINpassword
name: ad_id_range
base_id: 150000000
range_size: 200000
rid_base: 1000000
idrange_type: ipa-ad-trust
dom_sid: S-1-5-21-2870384104-3340008087-3140804251
auto_private_groups: "true"

View File

@@ -0,0 +1,14 @@
---
- name: Playbook to manage idrange
hosts: ipaserver
become: no
tasks:
- name: Ensure local idrange is present
ipaidrange:
ipaadmin_password: SomeADMINpassword
name: id_range
base_id: 150000000
range_size: 200000
rid_base: 1000000
secondary_rid_base: 200000000

View File

@@ -0,0 +1,8 @@
---
- name: Playbook to setup smartcard for IPA clients
hosts: ipaclients
become: true
roles:
- role: ipasmartcard_client
state: present

View File

@@ -0,0 +1,8 @@
---
- name: Playbook to setup smartcard for IPA replicas
hosts: ipareplicas
become: true
roles:
- role: ipasmartcard_server
state: present

View File

@@ -0,0 +1,8 @@
---
- name: Playbook to setup smartcard for IPA server
hosts: ipaserver
become: true
roles:
- role: ipasmartcard_server
state: present

View File

@@ -0,0 +1,8 @@
---
- name: Playbook to setup smartcard for IPA server and replicas
hosts: ipaserver, ipareplicas
become: true
roles:
- role: ipasmartcard_server
state: present

View File

@@ -6,5 +6,6 @@
tasks:
- name: Ensure privilege "Broad Privilege" is absent
ipaprivilege:
ipaadmin_password: SomeADMINpassword
name: Broad Privilege
state: absent

View File

@@ -0,0 +1,11 @@
---
- name: Servicedelegationrule absent example
hosts: ipaserver
become: no
tasks:
- name: Ensure servicedelegationrule test-delegation-rule is absent
ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: test-delegation-rule
state: absent

View File

@@ -0,0 +1,10 @@
---
- name: Servicedelegationrule present example
hosts: ipaserver
become: no
tasks:
- name: Ensure servicedelegationrule test-delegation-rule is present
ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: test-delegation-rule

View File

@@ -0,0 +1,13 @@
---
- name: Servicedelegationrule principal member absent example
hosts: ipaserver
become: no
tasks:
- name: Ensure principal member test/example.com is absent in servicedelegationrule test-delegation-rule
ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: test-delegation-rule
principal: test/example.com
action: member
state: absent

View File

@@ -0,0 +1,12 @@
---
- name: Servicedelegationrule principal member present example
hosts: ipaserver
become: no
tasks:
- name: Ensure principal member test/example.com is present in servicedelegationrule test-delegation-rule
ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: test-delegation-rule
principal: test/example.com
action: member

View File

@@ -0,0 +1,13 @@
---
- name: Servicedelegationrule absent example
hosts: ipaserver
become: no
tasks:
- name: Ensure member test/example.com is absent in servicedelegationrule test-delegation-rule
ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: test-delegation-rule
principal: test/example.com
action: member
state: absent

View File

@@ -0,0 +1,12 @@
---
- name: Servicedelegationrule member present example
hosts: ipaserver
become: no
tasks:
- name: Ensure member test/example.com is present in servicedelegationrule test-delegation-rule
ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: test-delegation-rule
principal: test/example.com
action: member

View File

@@ -0,0 +1,11 @@
---
- name: Servicedelegationtarget absent example
hosts: ipaserver
become: no
tasks:
- name: Ensure servicedelegationtarget test-delegation-target is absent
ipaservicedelegationtarget:
ipaadmin_password: SomeADMINpassword
name: test-delegation-target
state: absent

View File

@@ -0,0 +1,13 @@
---
- name: Servicedelegationtarget absent example
hosts: ipaserver
become: no
tasks:
- name: Ensure member test/example.com is absent in servicedelegationtarget test-delegation-target
ipaservicedelegationtarget:
ipaadmin_password: SomeADMINpassword
name: test-delegation-target
principal: test/example.com
action: member
state: absent

View File

@@ -0,0 +1,12 @@
---
- name: Servicedelegationtarget member present example
hosts: ipaserver
become: no
tasks:
- name: Ensure member test/example.com is present in servicedelegationtarget test-delegation-target
ipaservicedelegationtarget:
ipaadmin_password: SomeADMINpassword
name: test-delegation-target
principal: test/example.com
action: member

View File

@@ -0,0 +1,10 @@
---
- name: Servicedelegationtarget present example
hosts: ipaserver
become: no
tasks:
- name: Ensure servicedelegationtarget test-delegation-target is present
ipaservicedelegationtarget:
ipaadmin_password: SomeADMINpassword
name: test-delegation-target

View File

@@ -6,7 +6,7 @@
tasks:
- name: Ensure sudocmdgroup is absent
ipasudocmdgroup:
ipaadmin_password: pass1234
ipaadmin_password: SomeADMINpassword
name: network
state: absent
action: sudocmdgroup

View File

@@ -6,7 +6,7 @@
tasks:
- name: Ensure sudocmdgroup sudocmds are present
ipasudocmdgroup:
ipaadmin_password: pass1234
ipaadmin_password: SomeADMINpassword
name: network
description: Group of important commands.
sudocmd:

View File

@@ -6,6 +6,6 @@
tasks:
- name: Ensure sudorule command is absent
ipasudorule:
ipaadmin_password: pass1234
ipaadmin_password: SomeADMINpassword
name: testrule1
state: absent

View File

@@ -14,7 +14,7 @@
tasks:
- name: Add topology segment
ipatopologysegment:
ipaadmin_password: "{{ ipaadmin_password }}"
ipaadmin_password: SomeADMINpassword
suffix: "{{ item.suffix }}"
name: "{{ item.name | default(omit) }}"
left: "{{ item.left }}"

View File

@@ -14,7 +14,7 @@
tasks:
- name: Add topology segment
ipatopologysegment:
ipaadmin_password: "{{ ipaadmin_password }}"
ipaadmin_password: SomeADMINpassword
suffix: "{{ item.suffix }}"
name: "{{ item.name | default(omit) }}"
left: "{{ item.left }}"

View File

@@ -14,7 +14,7 @@
tasks:
- name: Add topology segment
ipatopologysegment:
ipaadmin_password: "{{ ipaadmin_password }}"
ipaadmin_password: SomeADMINpassword
suffix: "{{ item.suffix }}"
name: "{{ item.name | default(omit) }}"
left: "{{ item.left }}"

View File

@@ -6,6 +6,7 @@
tasks:
- name: ensure the trust is present
ipatrust:
ipaadmin_password: SomeADMINpassword
realm: windows.local
admin: Administrator
password: secret_password

View File

@@ -6,5 +6,6 @@
tasks:
- name: ensure the trust is absent
ipatrust:
ipaadmin_password: SomeADMINpassword
realm: windows.local
state: absent

View File

@@ -45,3 +45,13 @@ options:
type: bool
default: true
"""
DELETE_CONTINUE = r"""
options:
delete_continue:
description: |
Continuous mode. Don't stop on errors. Valid only if `state` is `absent`.
aliases: ["continue"]
type: bool
default: True
"""

View File

@@ -28,8 +28,8 @@ __metaclass__ = type
__all__ = ["gssapi", "netaddr", "api", "ipalib_errors", "Env",
"DEFAULT_CONFIG", "LDAP_GENERALIZED_TIME_FORMAT",
"kinit_password", "kinit_keytab", "run", "DN", "VERSION",
"paths", "get_credentials_if_valid", "Encoding",
"load_pem_x509_certificate", "DNSName"]
"paths", "tasks", "get_credentials_if_valid", "Encoding",
"load_pem_x509_certificate", "DNSName", "getargspec"]
import sys
@@ -48,29 +48,32 @@ else:
import gssapi
from datetime import datetime
from contextlib import contextmanager
import inspect
# Import getargspec from inspect or provide own getargspec for
# Python 2 compatibility with Python 3.11+.
try:
from inspect import getargspec
except ImportError:
from collections import namedtuple
from inspect import getfullargspec
# The code is copied from Python 3.10 inspect.py
# Authors: Ka-Ping Yee <ping@lfw.org>
# Yury Selivanov <yselivanov@sprymix.com>
ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults')
def getargspec(func):
args, varargs, varkw, defaults, kwonlyargs, _kwonlydefaults, \
ann = getfullargspec(func)
if kwonlyargs or ann:
raise ValueError(
"Function has keyword-only parameters or annotations"
", use inspect.signature() API which can support them")
return ArgSpec(args, varargs, varkw, defaults)
# ansible-freeipa requires locale to be C, IPA requires utf-8.
os.environ["LANGUAGE"] = "C"
try:
from packaging import version
except ImportError:
# If `packaging` not found, split version string for creating version
# object. Although it is not PEP 440 compliant, it will work for stable
# FreeIPA releases.
import re
class version: # pylint: disable=invalid-name, too-few-public-methods
@staticmethod
def parse(version_str):
"""
Split a version string A.B.C, into a tuple.
This will not work for `rc`, `dev` or similar version string.
"""
return tuple(re.split("[-_.]", version_str)) # noqa: W605
from ipalib import api
from ipalib import errors as ipalib_errors # noqa
from ipalib.config import Env
@@ -84,8 +87,10 @@ else:
from ipapython.dn import DN
from ipapython.version import VERSION
from ipaplatform.paths import paths
from ipaplatform.tasks import tasks
from ipalib.krb_utils import get_credentials_if_valid
from ipapython.dnsutil import DNSName
from ipapython import kerberos
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_text
from ansible.module_utils.common.text.converters import jsonify
@@ -138,6 +143,13 @@ else:
return fstore.has_files()
# Try to import dcerpc
try:
import ipaserver.dcerpc # pylint: disable=no-member
_dcerpc_bindings_installed = True # pylint: disable=invalid-name
except ImportError:
_dcerpc_bindings_installed = False # pylint: disable=invalid-name
if six.PY3:
unicode = str
@@ -220,6 +232,8 @@ else:
ldap_cache: Control use of LDAP cache layer. (bool)
"""
global _dcerpc_bindings_installed # pylint: disable=C0103,W0603
env = Env()
env._bootstrap()
env._finalize_core(**dict(DEFAULT_CONFIG))
@@ -251,6 +265,7 @@ else:
backend = api.Backend.ldap2
else:
backend = api.Backend.rpcclient
_dcerpc_bindings_installed = False
if not backend.isconnected():
backend.connect(ccache=os.environ.get('KRB5CCNAME', None))
@@ -288,8 +303,8 @@ else:
operation = oper_map.get(oper)
if not operation:
raise NotImplementedError("Invalid operator: %s" % oper)
return operation(version.parse(VERSION),
version.parse(requested_version))
return operation(tasks.parse_ipa_version(VERSION),
tasks.parse_ipa_version(requested_version))
def date_format(value):
accepted_date_formats = [
@@ -309,15 +324,49 @@ else:
raise ValueError("Invalid date '%s'" % value)
def compare_args_ipa(module, args, ipa, ignore=None): # noqa
"""Compare IPA obj attrs with the command args.
"""Compare IPA object attributes against command arguments.
This function compares IPA objects attributes with the args the
module is intending to use to call a command. ignore can be a list
of attributes, that should be ignored in the comparison.
This is useful to know if a call to IPA server will be needed or not.
In order to compare we have to perform slight changes in data formats.
This function compares 'ipa' attributes with the 'args' the module
is intending to use as parameters to an IPA API command. A list of
attribute names that should be ignored during comparison may be
provided.
Returns True if they are the same and False otherwise.
The comparison will be performed on every attribute provided in
'args'. If the attribute in 'args' or 'ipa' is not a scalar value
(including strings) the comparison will be performed as if the
attribute is a set of values, so duplicate values will count as a
single one. If both values are scalar values, then a direct
comparison is performed.
If an attribute is not available in 'ipa', its value is considered
to be a list with an empty string (['']), possibly forcing the
conversion of the 'args' attribute to a list for comparison. This
allows, for example, the usage of empty strings which should compare
as equals to inexistent attributes (None), as is done in IPA API.
This function is mostly useful to evaluate the need of a call to
IPA server when provided arguments are equivalent to the existing
values for a given IPA object.
Parameters
----------
module: AnsibleModule
The AnsibleModule used to log debug messages.
args: dict
The set of attributes provided by the playbook task.
ipa: dict
The set of attributes from the IPA object retrieved.
ignore: list
An optional list of attribute names that should be ignored and
not evaluated.
Return
------
True is returned if all attribute values in 'args' are
equivalent to the corresponding attribute value in 'ipa'.
"""
base_debug_msg = "Ansible arguments and IPA commands differed. "
@@ -337,52 +386,45 @@ else:
filtered_args = [key for key in args if key not in ignore]
for key in filtered_args:
if key not in ipa: # pylint: disable=no-else-return
module.debug(
base_debug_msg + "Command key not present in IPA: %s" % key
)
return False
arg = args[key]
ipa_arg = ipa.get(key, [""])
# If ipa_arg is a list and arg is not, replace arg
# with list containing arg. Most args in a find result
# are lists, but not all.
if isinstance(ipa_arg, (list, tuple)):
if not isinstance(arg, list):
arg = [arg]
if len(ipa_arg) != len(arg):
module.debug(
base_debug_msg
+ "List length doesn't match for key %s: %d %d"
% (key, len(arg), len(ipa_arg),)
)
return False
# ensure list elements types are the same.
if not (
isinstance(ipa_arg[0], type(arg[0]))
or isinstance(arg[0], type(ipa_arg[0]))
):
arg = [to_text(_arg) for _arg in arg]
try:
arg_set = set(arg)
ipa_arg_set = set(ipa_arg)
except TypeError:
if arg != ipa_arg:
module.debug(
base_debug_msg
+ "Different values: %s %s" % (arg, ipa_arg)
)
return False
else:
arg = args[key]
ipa_arg = ipa[key]
# If ipa_arg is a list and arg is not, replace arg
# with list containing arg. Most args in a find result
# are lists, but not all.
if isinstance(ipa_arg, tuple):
ipa_arg = list(ipa_arg)
if isinstance(ipa_arg, list):
if not isinstance(arg, list):
arg = [arg]
if len(ipa_arg) != len(arg):
module.debug(
base_debug_msg
+ "List length doesn't match for key %s: %d %d"
% (key, len(arg), len(ipa_arg),)
)
return False
if isinstance(ipa_arg[0], str) and isinstance(arg[0], int):
arg = [to_text(_arg) for _arg in arg]
if isinstance(ipa_arg[0], unicode) \
and isinstance(arg[0], int):
arg = [to_text(_arg) for _arg in arg]
try:
arg_set = set(arg)
ipa_arg_set = set(ipa_arg)
except TypeError:
if arg != ipa_arg:
module.debug(
base_debug_msg
+ "Different values: %s %s" % (arg, ipa_arg)
)
return False
else:
if arg_set != ipa_arg_set:
module.debug(
base_debug_msg
+ "Different set content: %s %s"
% (arg_set, ipa_arg_set,)
)
return False
if arg_set != ipa_arg_set:
module.debug(
base_debug_msg
+ "Different set content: %s %s"
% (arg_set, ipa_arg_set,)
)
return False
return True
def _afm_convert(value):
@@ -397,11 +439,32 @@ else:
return value
def module_params_get(module, name):
return _afm_convert(module.params.get(name))
def module_params_get_lowercase(module, name):
def module_params_get(module, name, allow_empty_string=False):
value = _afm_convert(module.params.get(name))
# Fail on empty strings in the list or if allow_empty_string is True
# if there is another entry in the list together with the empty
# string.
# Due to an issue in Ansible it is possible to use the empty string
# "" for lists with choices, even if the empty list is not part of
# the choices.
# Ansible issue https://github.com/ansible/ansible/issues/77108
if isinstance(value, list):
for val in value:
if isinstance(val, (str, unicode)) and not val:
if not allow_empty_string:
module.fail_json(
msg="Parameter '%s' contains an empty string" %
name)
elif len(value) > 1:
module.fail_json(
msg="Parameter '%s' may not contain another "
"entry together with an empty string" % name)
return value
def module_params_get_lowercase(module, name, allow_empty_string=False):
value = module_params_get(module, name, allow_empty_string)
if isinstance(value, list):
value = [v.lower() for v in value]
if isinstance(value, (str, unicode)):
@@ -550,6 +613,89 @@ else:
return False
return True
def servicedelegation_normalize_principals(module, principal,
check_exists=False):
"""
Normalize servicedelegation principals.
The principals can be service and with IPA 4.9.0+ also host principals.
"""
def _normalize_principal_name(name, realm):
# Normalize principal name
# Copied from ipaserver/plugins/servicedelegation.py
try:
princ = kerberos.Principal(name, realm=realm)
except ValueError as _err:
raise ipalib_errors.ValidationError(
name='principal',
reason="Malformed principal: %s" % str(_err))
if len(princ.components) == 1 and \
not princ.components[0].endswith('$'):
nprinc = 'host/' + unicode(princ)
else:
nprinc = unicode(princ)
return nprinc
def _check_exists(module, _type, name):
# Check if item of type _type exists using the show command
try:
module.ipa_command("%s_show" % _type, name, {})
except ipalib_errors.NotFound as e:
msg = str(e)
if "%s not found" % _type in msg:
return False
module.fail_json(msg="%s_show failed: %s" % (_type, msg))
return True
ipa_realm = module.ipa_get_realm()
_principal = []
for _princ in principal:
princ = _princ
realm = ipa_realm
# Get principal and realm from _princ if there is a realm
if '@' in _princ:
princ, realm = _princ.rsplit('@', 1)
# Lowercase principal
princ = princ.lower()
# Normalize principal
try:
nprinc = _normalize_principal_name(princ, realm)
except ipalib_errors.ValidationError as err:
module.fail_json(msg="%s: %s" % (_princ, str(err)))
princ = unicode(nprinc)
# Check that host principal exists
if princ.startswith("host/"):
if module.ipa_check_version("<", "4.9.0"):
module.fail_json(
msg="The use of host principals is not supported "
"by your IPA version")
# Get host FQDN (no leading 'host/' and no trailing realm)
# (There is no removeprefix and removesuffix in Python2)
_host = princ[5:]
if _host.endswith("@%s" % realm):
_host = _host[:-len(realm) - 1]
# Seach for host
if check_exists and not _check_exists(module, "host", _host):
module.fail_json(msg="Host '%s' does not exist" % _host)
# Check the service principal exists
else:
if check_exists and \
not _check_exists(module, "service", princ):
module.fail_json(msg="Service %s does not exist" % princ)
_principal.append(princ)
return _principal
def exit_raw_json(module, **kwargs):
"""
Print the raw parameters in JSON format, without masking.
@@ -569,6 +715,42 @@ else:
print(jsonify(kwargs))
sys.exit(0)
def __get_domain_validator():
if not _dcerpc_bindings_installed:
raise ipalib_errors.NotFound(
reason=(
'Cannot perform SID validation without Samba 4 support '
'installed. Make sure you have installed server-trust-ad '
'sub-package of IPA on the server'
)
)
# pylint: disable=no-member
domain_validator = ipaserver.dcerpc.DomainValidator(api)
# pylint: enable=no-member
if not domain_validator.is_configured():
raise ipalib_errors.NotFound(
reason=(
'Cross-realm trusts are not configured. Make sure you '
'have run ipa-adtrust-install on the IPA server first'
)
)
return domain_validator
def get_trusted_domain_sid_from_name(dom_name):
"""
Given a trust domain name, returns the domain SID.
Returns unicode string representation for a given trusted domain name
or None if SID for the given trusted domain name could not be found.
"""
domain_validator = __get_domain_validator()
sid = domain_validator.get_sid_from_domain_name(dom_name)
return unicode(sid) if sid is not None else None
class IPAParamMapping(Mapping):
"""
Provides IPA API mapping to playbook parameters or computed values.
@@ -667,7 +849,10 @@ else:
# Check if param_name is actually a param
if param_name in self.ansible_module.params:
value = self.ansible_module.params_get(param_name)
if isinstance(value, bool):
if (
self.ansible_module.ipa_check_version("<", "4.9.10")
and isinstance(value, bool)
):
value = "TRUE" if value else "FALSE"
# Since param wasn't a param check if it's a method name
@@ -742,6 +927,12 @@ else:
ipaapi_ldap_cache=dict(type="bool", default="True"),
)
ipa_module_options_spec = dict(
delete_continue=dict(
type="bool", default=True, aliases=["continue"]
)
)
def __init__(self, *args, **kwargs):
# Extend argument_spec with ipa_module_base_spec
if "argument_spec" in kwargs:
@@ -749,6 +940,16 @@ else:
_spec.update(self.ipa_module_base_spec)
kwargs["argument_spec"] = _spec
if "ipa_module_options" in kwargs:
_update = {
k: self.ipa_module_options_spec[k]
for k in kwargs["ipa_module_options"]
}
_spec = kwargs.get("argument_spec", {})
_spec.update(_update)
kwargs["argument_spec"] = _spec
del kwargs["ipa_module_options"]
# pylint: disable=super-with-arguments
super(IPAAnsibleModule, self).__init__(*args, **kwargs)
@@ -797,7 +998,7 @@ else:
finally:
temp_kdestroy(ccache_dir, ccache_name)
def params_get(self, name):
def params_get(self, name, allow_empty_string=False):
"""
Retrieve value set for module parameter.
@@ -805,11 +1006,13 @@ else:
----------
name: string
The name of the parameter to retrieve.
allow_empty_string: bool
The parameter allowes to have empty strings in a list
"""
return module_params_get(self, name)
return module_params_get(self, name, allow_empty_string)
def params_get_lowercase(self, name):
def params_get_lowercase(self, name, allow_empty_string=False):
"""
Retrieve value set for module parameter as lowercase, if not None.
@@ -817,9 +1020,11 @@ else:
----------
name: string
The name of the parameter to retrieve.
allow_empty_string: bool
The parameter allowes to have empty strings in a list
"""
return module_params_get_lowercase(self, name)
return module_params_get_lowercase(self, name, allow_empty_string)
def params_fail_used_invalid(self, invalid_params, state, action=None):
"""
@@ -875,10 +1080,11 @@ else:
"""
return api_command_no_name(self, command, args)
@staticmethod
def ipa_get_domain():
def ipa_get_domain(self):
"""Retrieve IPA API domain."""
return api_get_domain()
if not hasattr(self, "__ipa_api_domain"):
setattr(self, "__ipa_api_domain", api_get_domain())
return getattr(self, "__ipa_api_domain")
@staticmethod
def ipa_get_realm():
@@ -1029,7 +1235,7 @@ else:
elif result_handler is not None:
if "errors" not in handlers_user_args:
# pylint: disable=deprecated-method
argspec = inspect.getargspec(result_handler)
argspec = getargspec(result_handler)
if "errors" in argspec.args:
handlers_user_args["errors"] = _errors

View File

@@ -587,7 +587,6 @@ def main():
commands.append([None,
'automember_default_group_remove',
{'type': automember_type}])
ansible_module.warn("commands: %s" % repr(commands))
else:
dn_default_group = [DN(('cn', default_group),

View File

@@ -0,0 +1,235 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Authors:
# Chris Procter <cprocter@redhat.com>
#
# Copyright (C) 2021 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: ipaautomountkey
author: chris procter
short_description: Manage FreeIPA autommount map
description:
- Add, delete, and modify an IPA automount map
options:
ipaadmin_principal:
description: The admin principal
default: admin
ipaadmin_password:
description: The admin password
required: False
location:
description: automount location map is in
required: True
choices: ["automountlocationcn", "automountlocation"]
mapname:
description: automount map to be managed
choices: ["map", "automountmapname", "automountmap"]
required: True
key:
description: automount key to be managed
required: True
choices: ["name", "automountkey"]
newkey:
description: key to change to if state is 'renamed'
required: True
choices: ["newname", "newautomountkey"]
info:
description: Mount information for the key
required: True
choices: ["information", "automountinformation"]
state:
description: State to ensure
required: False
default: present
choices: ["present", "absent", "renamed"]
'''
EXAMPLES = '''
- name: create key TestKey
ipaautomountkey:
ipaadmin_password: SomeADMINpassword
locationcn: TestLocation
mapname: TestMap
key: TestKey
info: 192.168.122.1:/exports
state: present
- name: ensure key TestKey is absent
ipaautomountkey:
ipaadmin_password: SomeADMINpassword
location: TestLocation
mapname: TestMap
key: TestKey
state: absent
'''
RETURN = '''
'''
from ansible.module_utils.ansible_freeipa_module import (
IPAAnsibleModule, ipalib_errors
)
class AutomountKey(IPAAnsibleModule):
def __init__(self, *args, **kwargs):
# pylint: disable=super-with-arguments
super(AutomountKey, self).__init__(*args, **kwargs)
self.commands = []
def get_key(self, location, mapname, key):
try:
args = {
"automountmapautomountmapname": mapname,
"automountkey": key,
"all": True,
}
resp = self.ipa_command("automountkey_show", location, args)
except ipalib_errors.NotFound:
return None
else:
return resp.get("result")
def check_ipa_params(self):
invalid = []
state = self.params_get("state")
if state == "present":
invalid = ["rename"]
if not self.params_get("info"):
self.fail_json(msg="Value required for argument 'info'")
if state == "rename":
invalid = ["info"]
if not self.params_get("rename"):
self.fail_json(msg="Value required for argument 'renamed'")
if state == "absent":
invalid = ["info", "rename"]
self.params_fail_used_invalid(invalid, state)
@staticmethod
def get_args(mapname, key, info, rename):
_args = {}
if mapname:
_args["automountmapautomountmapname"] = mapname
if key:
_args["automountkey"] = key
if info:
_args["automountinformation"] = info
if rename:
_args["rename"] = rename
return _args
def define_ipa_commands(self):
state = self.params_get("state")
location = self.params_get("location")
mapname = self.params_get("mapname")
key = self.params_get("key")
info = self.params_get("info")
rename = self.params_get("rename")
args = self.get_args(mapname, key, info, rename)
res_find = self.get_key(location, mapname, key)
if state == "present":
if res_find is None:
# does not exist and is wanted
self.commands.append([location, "automountkey_add", args])
else:
# exists and is wanted, check for changes
if info not in res_find.get("automountinformation"):
self.commands.append([location, "automountkey_mod", args])
if state == "renamed":
if res_find is None:
self.fail_json(
msg=(
"Cannot rename inexistent key: '%s', '%s', '%s'"
% (location, mapname, key)
)
)
self.commands.append([location, "automountkey_mod", args])
if state == "absent":
# if key exists and self.ipa_params.state == "absent":
if res_find is not None:
self.commands.append([location, "automountkey_del", args])
def main():
ipa_module = AutomountKey(
argument_spec=dict(
state=dict(
type='str',
choices=['present', 'absent', 'renamed'],
required=None,
default='present',
),
location=dict(
type="str",
aliases=["automountlocationcn", "automountlocation"],
required=True,
),
rename=dict(
type="str",
aliases=["new_name", "newautomountkey"],
required=False,
),
mapname=dict(
type="str",
aliases=["map", "automountmapname", "automountmap"],
required=True,
),
key=dict(
type="str",
aliases=["name", "automountkey"],
required=True,
),
info=dict(
type="str",
aliases=["information", "automountinformation"],
required=False,
),
),
)
ipaapi_context = ipa_module.params_get("ipaapi_context")
with ipa_module.ipa_connect(context=ipaapi_context):
ipa_module.check_ipa_params()
ipa_module.define_ipa_commands()
changed = ipa_module.execute_ipa_commands(ipa_module.commands)
ipa_module.exit_json(changed=changed)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,197 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Authors:
# Chris Procter <cprocter@redhat.com>
#
# Copyright (C) 2021 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: ipaautomountmap
author: Chris Procter
short_description: Manage FreeIPA autommount map
description:
- Add, delete, and modify an IPA automount map
options:
ipaadmin_principal:
description: The admin principal.
default: admin
ipaadmin_password:
description: The admin password.
required: false
automountlocation:
description: automount location map is anchored to
choices: ["location", "automountlocationcn"]
required: True
name:
description: automount map to be managed.
choices: ["mapname", "map", "automountmapname"]
required: True
desc:
description: description of automount map.
choices: ["description"]
required: false
state:
description: State to ensure
required: false
default: present
choices: ["present", "absent"]
'''
EXAMPLES = '''
- name: ensure map named auto.DMZ in location DMZ is present
ipaautomountmap:
ipaadmin_password: SomeADMINpassword
name: auto.DMZ
location: DMZ
desc: "this is a map for servers in the DMZ"
- name: remove a map named auto.DMZ in location DMZ if it exists
ipaautomountmap:
ipaadmin_password: SomeADMINpassword
name: auto.DMZ
location: DMZ
state: absent
'''
RETURN = '''
'''
from ansible.module_utils.ansible_freeipa_module import (
IPAAnsibleModule, compare_args_ipa
)
class AutomountMap(IPAAnsibleModule):
def __init__(self, *args, **kwargs):
# pylint: disable=super-with-arguments
super(AutomountMap, self).__init__(*args, **kwargs)
self.commands = []
def get_automountmap(self, location, name):
try:
response = self.ipa_command(
"automountmap_show",
location,
{"automountmapname": name, "all": True}
)
except Exception: # pylint: disable=broad-except
return None
else:
return response["result"]
def check_ipa_params(self):
invalid = []
name = self.params_get("name")
state = self.params_get("state")
if state == "present":
if len(name) != 1:
self.fail_json(msg="Exactly one name must be provided for"
" 'state: present'.")
if state == "absent":
if len(name) == 0:
self.fail_json(msg="At least one 'name' must be provided for"
" 'state: absent'")
invalid = ["desc"]
self.params_fail_used_invalid(invalid, state)
def get_args(self, mapname, desc): # pylint: disable=no-self-use
# automountmapname is required for all automountmap operations.
if not mapname:
self.fail_json(msg="automountmapname cannot be None or empty.")
_args = {"automountmapname": mapname}
# An empty string is valid and will clear the attribute.
if desc is not None:
_args["description"] = desc
return _args
def define_ipa_commands(self):
name = self.params_get("name")
state = self.params_get("state")
location = self.params_get("location")
desc = self.params_get("desc")
for mapname in name:
automountmap = self.get_automountmap(location, mapname)
if state == "present":
args = self.get_args(mapname, desc)
if automountmap is None:
self.commands.append([location, "automountmap_add", args])
else:
if not compare_args_ipa(self, args, automountmap):
self.commands.append(
[location, "automountmap_mod", args]
)
if state == "absent":
if automountmap is not None:
self.commands.append([
location,
"automountmap_del",
{"automountmapname": [mapname]}
])
def main():
ipa_module = AutomountMap(
argument_spec=dict(
state=dict(type='str',
default='present',
choices=['present', 'absent']
),
location=dict(type="str",
aliases=["automountlocation", "automountlocationcn"],
default=None,
required=True
),
name=dict(type="list",
aliases=["mapname", "map", "automountmapname"],
default=None,
required=True
),
desc=dict(type="str",
aliases=["description"],
required=False,
default=None
),
),
)
changed = False
ipaapi_context = ipa_module.params_get("ipaapi_context")
with ipa_module.ipa_connect(context=ipaapi_context):
ipa_module.check_ipa_params()
ipa_module.define_ipa_commands()
changed = ipa_module.execute_ipa_commands(ipa_module.commands)
ipa_module.exit_json(changed=changed)
if __name__ == "__main__":
main()

View File

@@ -346,11 +346,13 @@ def main():
"ca_renewal_master_server": "ca_renewal_master_server",
"domain_resolution_order": "ipadomainresolutionorder"
}
allow_empty_string = ["pac_type", "user_auth_type", "configstring"]
reverse_field_map = {v: k for k, v in field_map.items()}
params = {}
for x in field_map:
val = ansible_module.params_get(x)
val = ansible_module.params_get(
x, allow_empty_string=(x in allow_empty_string))
if val is not None:
params[field_map.get(x, x)] = val
@@ -401,6 +403,10 @@ def main():
k: v for k, v in params.items()
if k not in result or result[k] != v
}
# Remove empty string args from params if result arg is not set
for k in ["ipakrbauthzdata", "ipauserauthtype", "ipaconfigstring"]:
if k not in result and k in params and params[k] == [""]:
del params[k]
if params \
and not compare_args_ipa(ansible_module, params, result):
changed = True
@@ -435,12 +441,23 @@ def main():
elif (
isinstance(value, (tuple, list)) and arg_type == "bool"
):
exit_args[k] = (value[0] == "TRUE")
# FreeIPA 4.9.10+ and 4.10 use proper mapping for
# 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")
else:
if arg_type not in type_map:
raise ValueError(
"Unexpected attribute type: %s" % arg_type)
exit_args[k] = type_map[arg_type](value)
# Add empty pac_type and user_auth_type if they are not set
for key in ["pac_type", "user_auth_type"]:
if key not in exit_args:
exit_args[key] = ""
# Add empty domain_resolution_order if it is not set
if "domain_resolution_order" not in exit_args:
exit_args["domain_resolution_order"] = []
# Done
ansible_module.exit_json(changed=changed, config=exit_args)

View File

@@ -54,13 +54,22 @@ options:
global forwarders.
required: false
choices: ['only', 'first', 'none']
alias: ["forwardpolicy"]
allow_sync_ptr:
description:
Allow synchronization of forward (A, AAAA) and reverse (PTR) records.
required: false
type: bool
action:
description: |
Work on dnsconfig or member level. It can be one of `member` or
`dnsconfig`. Only `forwarders` can be managed with `action: member`.
default: "dnsconfig"
choices: ["member", "dnsconfig"]
state:
description: State to ensure
description: |
The state to ensure. It can be one of `present` or `absent`.
`absent` can only be used with `action: member` and `forwarders`.
default: present
choices: ["present", "absent"]
"""
@@ -83,6 +92,7 @@ EXAMPLES = """
- ip_address: 2001:4860:4860::8888
port: 53
state: absent
action: member
# Disable PTR record synchronization.
- ipadnsconfig:
@@ -118,7 +128,7 @@ def find_dnsconfig(module):
return None
def gen_args(module, state, dnsconfig, forwarders, forward_policy,
def gen_args(module, state, action, dnsconfig, forwarders, forward_policy,
allow_sync_ptr):
_args = {}
@@ -137,15 +147,20 @@ def gen_args(module, state, dnsconfig, forwarders, forward_policy,
global_forwarders = dnsconfig.get('idnsforwarders', [])
if state == 'absent':
_args['idnsforwarders'] = [
fwd for fwd in global_forwarders if fwd not in _forwarders]
# When all forwarders should be excluded, use an empty string ('').
if not _args['idnsforwarders']:
_args['idnsforwarders'] = ['']
if action == "member":
_args['idnsforwarders'] = [
fwd for fwd in global_forwarders if fwd not in _forwarders]
# When all forwarders should be excluded,
# use an empty string ('').
if not _args['idnsforwarders']:
_args['idnsforwarders'] = ['']
elif state == 'present':
_args['idnsforwarders'] = [
fwd for fwd in _forwarders if fwd not in global_forwarders]
if action == "member":
_args['idnsforwarders'] = \
list(set(list(_forwarders) + list(global_forwarders)))
else:
_args['idnsforwarders'] = _forwarders
# If no forwarders should be added, remove argument.
if not _args['idnsforwarders']:
del _args['idnsforwarders']
@@ -158,7 +173,10 @@ def gen_args(module, state, dnsconfig, forwarders, forward_policy,
_args['idnsforwardpolicy'] = forward_policy
if allow_sync_ptr is not None:
_args['idnsallowsyncptr'] = 'TRUE' if allow_sync_ptr else 'FALSE'
if module.ipa_check_version("<", "4.9.10"):
_args['idnsallowsyncptr'] = "TRUE" if allow_sync_ptr else "FALSE"
else:
_args['idnsallowsyncptr'] = allow_sync_ptr
return _args
@@ -175,13 +193,17 @@ def main():
forwarders=dict(type='list', default=None, required=False,
options=dict(**forwarder_spec)),
forward_policy=dict(type='str', required=False, default=None,
choices=['only', 'first', 'none']),
choices=['only', 'first', 'none'],
aliases=["forwardpolicy"]),
allow_sync_ptr=dict(type='bool', required=False, default=None),
# general
action=dict(type="str", default="dnsconfig",
choices=["member", "dnsconfig"]),
state=dict(type="str", default="present",
choices=["present", "absent"]),
)
),
supports_check_mode=True,
)
ansible_module._ansible_debug = True
@@ -191,11 +213,17 @@ def main():
forward_policy = ansible_module.params_get('forward_policy')
allow_sync_ptr = ansible_module.params_get('allow_sync_ptr')
action = ansible_module.params_get('action')
state = ansible_module.params_get('state')
# Check parameters.
invalid = []
if state == "present" and action == "member":
invalid = ['forward_policy', 'allow_sync_ptr']
if state == 'absent':
if action != "member":
ansible_module.fail_json(
msg="State 'absent' is only valid with action 'member'.")
invalid = ['forward_policy', 'allow_sync_ptr']
ansible_module.params_fail_used_invalid(invalid, state)
@@ -208,7 +236,7 @@ def main():
with ansible_module.ipa_connect():
res_find = find_dnsconfig(ansible_module)
args = gen_args(ansible_module, state, res_find, forwarders,
args = gen_args(ansible_module, state, action, res_find, forwarders,
forward_policy, allow_sync_ptr)
# Execute command only if configuration changes.

View File

@@ -68,7 +68,7 @@ options:
required: false
default: only
choices: ["only", "first", "none"]
aliases: ["idnsforwarders"]
aliases: ["idnsforwarders", "forward_policy"]
skip_overlap_check:
description:
- Force DNS zone creation even if it will overlap with an existing zone.
@@ -189,7 +189,8 @@ def main():
port=dict(type='int', required=False,
default=None),
)),
forwardpolicy=dict(type='str', aliases=["idnsforwardpolicy"],
forwardpolicy=dict(type='str',
aliases=["idnsforwardpolicy", "forward_policy"],
required=False,
choices=['only', 'first', 'none']),
skip_overlap_check=dict(type='bool', required=False),
@@ -343,7 +344,13 @@ def main():
if state in ['enabled', 'disabled']:
if existing_resource is not None:
is_enabled = existing_resource["idnszoneactive"][0]
# FreeIPA 4.9.10+ and 4.10 use proper mapping for
# boolean values, so we need to convert it to str
# for comparison.
# See: https://github.com/freeipa/freeipa/pull/6294
is_enabled = (
str(existing_resource["idnszoneactive"][0]).upper()
)
else:
ansible_module.fail_json(
msg="dnsforwardzone '%s' not found." % (name))

View File

@@ -418,7 +418,11 @@ class DNSZoneModule(IPAAnsibleModule):
is_zone_active = False
else:
zone = response["result"]
is_zone_active = "TRUE" in zone.get("idnszoneactive")
# FreeIPA 4.9.10+ and 4.10 use proper mapping for boolean vaalues.
# See: https://github.com/freeipa/freeipa/pull/6294
is_zone_active = (
str(zone.get("idnszoneactive")[0]).upper() == "TRUE"
)
return zone, is_zone_active

View File

@@ -97,6 +97,11 @@ options:
required: false
type: list
ailases: ["ipaexternalmember", "external_member"]
idoverrideuser:
description:
- User ID overrides to add
required: false
type: list
action:
description: Work on group or member level
default: group
@@ -181,9 +186,10 @@ EXAMPLES = """
RETURN = """
"""
from ansible.module_utils._text import to_text
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, \
gen_add_list, gen_intersection_list
gen_add_list, gen_intersection_list, api_check_param
def find_group(module, name):
@@ -198,7 +204,14 @@ def find_group(module, name):
module.fail_json(
msg="There is more than one group '%s'" % (name))
elif len(_result["result"]) == 1:
return _result["result"][0]
_res = _result["result"][0]
# The returned services are of type ipapython.kerberos.Principal,
# also services are not case sensitive. Therefore services are
# converted to lowercase strings to be able to do the comparison.
if "member_service" in _res:
_res["member_service"] = \
[to_text(svc).lower() for svc in _res["member_service"]]
return _res
return None
@@ -215,7 +228,7 @@ def gen_args(description, gid, nomembers):
return _args
def gen_member_args(user, group, service, externalmember):
def gen_member_args(user, group, service, externalmember, idoverrideuser):
_args = {}
if user is not None:
_args["member_user"] = user
@@ -225,6 +238,8 @@ def gen_member_args(user, group, service, externalmember):
_args["member_service"] = service
if externalmember is not None:
_args["member_external"] = externalmember
if idoverrideuser is not None:
_args["member_idoverrideuser"] = idoverrideuser
return _args
@@ -272,6 +287,7 @@ def main():
user=dict(required=False, type='list', default=None),
group=dict(required=False, type='list', default=None),
service=dict(required=False, type='list', default=None),
idoverrideuser=dict(required=False, type='list', default=None),
membermanager_user=dict(required=False, type='list', default=None),
membermanager_group=dict(required=False, type='list',
default=None),
@@ -304,11 +320,13 @@ def main():
gid = ansible_module.params_get("gid")
nonposix = ansible_module.params_get("nonposix")
external = ansible_module.params_get("external")
idoverrideuser = ansible_module.params_get("idoverrideuser")
posix = ansible_module.params_get("posix")
nomembers = ansible_module.params_get("nomembers")
user = ansible_module.params_get("user")
group = ansible_module.params_get("group")
service = ansible_module.params_get("service")
# Services are not case sensitive
service = ansible_module.params_get_lowercase("service")
membermanager_user = ansible_module.params_get("membermanager_user")
membermanager_group = ansible_module.params_get("membermanager_group")
externalmember = ansible_module.params_get("externalmember")
@@ -370,12 +388,27 @@ def main():
"by your IPA version"
)
has_idoverrideuser = api_check_param(
"group_add_member", "idoverrideuser")
if idoverrideuser is not None and not has_idoverrideuser:
ansible_module.fail_json(
msg="Managing a idoverrideuser as part of a group is not "
"supported by your IPA version")
commands = []
for name in names:
# Make sure group exists
res_find = find_group(ansible_module, name)
user_add, user_del = [], []
group_add, group_del = [], []
service_add, service_del = [], []
externalmember_add, externalmember_del = [], []
idoverrides_add, idoverrides_del = [], []
membermanager_user_add, membermanager_user_del = [], []
membermanager_group_add, membermanager_group_del = [], []
# Create command
if state == "present":
# Can't change an existing posix group
@@ -422,7 +455,7 @@ def main():
res_find["objectclass"].append("posixgroup")
member_args = gen_member_args(
user, group, service, externalmember
user, group, service, externalmember, idoverrideuser
)
if not compare_args_ipa(ansible_module, member_args,
res_find):
@@ -440,44 +473,11 @@ def main():
externalmember_del) = gen_add_del_lists(
externalmember, res_find.get("member_external"))
# setup member args for add/remove members.
add_member_args = {
"user": user_add,
"group": group_add,
}
del_member_args = {
"user": user_del,
"group": group_del,
}
if has_add_member_service:
add_member_args["service"] = service_add
del_member_args["service"] = service_del
if is_external_group(res_find):
add_member_args["ipaexternalmember"] = \
externalmember_add
del_member_args["ipaexternalmember"] = \
externalmember_del
elif externalmember or external:
ansible_module.fail_json(
msg="Cannot add external members to a "
"non-external group."
)
# Add members
add_members = any([user_add, group_add,
service_add, externalmember_add])
if add_members:
commands.append(
[name, "group_add_member", add_member_args]
)
# Remove members
remove_members = any([user_del, group_del,
service_del, externalmember_del])
if remove_members:
commands.append(
[name, "group_remove_member", del_member_args]
)
(idoverrides_add,
idoverrides_del) = gen_add_del_lists(
idoverrideuser,
res_find.get("member_idoverrideuser")
)
membermanager_user_add, membermanager_user_del = \
gen_add_del_lists(
@@ -491,93 +491,32 @@ def main():
res_find.get("membermanager_group")
)
if has_add_membermanager:
# Add membermanager users and groups
if len(membermanager_user_add) > 0 or \
len(membermanager_group_add) > 0:
commands.append(
[name, "group_add_member_manager",
{
"user": membermanager_user_add,
"group": membermanager_group_add,
}]
)
# Remove member manager
if len(membermanager_user_del) > 0 or \
len(membermanager_group_del) > 0:
commands.append(
[name, "group_remove_member_manager",
{
"user": membermanager_user_del,
"group": membermanager_group_del,
}]
)
elif action == "member":
if res_find is None:
ansible_module.fail_json(msg="No group '%s'" % name)
add_member_args = {
"user": user,
"group": group,
}
if has_add_member_service:
add_member_args["service"] = service
if is_external_group(res_find):
add_member_args["ipaexternalmember"] = externalmember
elif externalmember:
ansible_module.fail_json(
msg="Cannot add external members to a "
"non-external group."
)
# Reduce add lists for member_user, member_group,
# member_service and member_external to new entries
# only that are not in res_find.
if user is not None and "member_user" in res_find:
user = gen_add_list(
user, res_find["member_user"])
if group is not None and "member_group" in res_find:
group = gen_add_list(
group, res_find["member_group"])
if service is not None and "member_service" in res_find:
service = gen_add_list(
service, res_find["member_service"])
if externalmember is not None \
and "member_external" in res_find:
externalmember = gen_add_list(
externalmember, res_find["member_external"])
user_add = gen_add_list(
user, res_find.get("member_user"))
group_add = gen_add_list(
group, res_find.get("member_group"))
service_add = gen_add_list(
service, res_find.get("member_service"))
externalmember_add = gen_add_list(
externalmember, res_find.get("member_external"))
idoverrides_add = gen_add_list(
idoverrideuser, res_find.get("member_idoverrideuser"))
if any([user, group, service, externalmember]):
commands.append(
[name, "group_add_member", add_member_args]
)
if has_add_membermanager:
# Reduce add list for membermanager_user and
# membermanager_group to new entries only that are
# not in res_find.
if membermanager_user is not None \
and "membermanager_user" in res_find:
membermanager_user = gen_add_list(
membermanager_user,
res_find["membermanager_user"])
if membermanager_group is not None \
and "membermanager_group" in res_find:
membermanager_group = gen_add_list(
membermanager_group,
res_find["membermanager_group"])
# Add membermanager users and groups
if membermanager_user is not None or \
membermanager_group is not None:
commands.append(
[name, "group_add_member_manager",
{
"user": membermanager_user,
"group": membermanager_group,
}]
)
membermanager_user_add = gen_add_list(
membermanager_user,
res_find.get("membermanager_user")
)
membermanager_group_add = gen_add_list(
membermanager_group,
res_find.get("membermanager_group")
)
elif state == "absent":
if action == "group":
@@ -588,70 +527,100 @@ def main():
if res_find is None:
ansible_module.fail_json(msg="No group '%s'" % name)
del_member_args = {
"user": user,
"group": group,
}
if has_add_member_service:
del_member_args["service"] = service
if is_external_group(res_find):
del_member_args["ipaexternalmember"] = externalmember
elif externalmember:
if not is_external_group(res_find) and externalmember:
ansible_module.fail_json(
msg="Cannot add external members to a "
"non-external group."
)
# Reduce del lists of member_user, member_group,
# member_service and member_external to the entries only
# that are in res_find.
if user is not None:
user = gen_intersection_list(
user, res_find.get("member_user"))
if group is not None:
group = gen_intersection_list(
group, res_find.get("member_group"))
if service is not None:
service = gen_intersection_list(
service, res_find.get("member_service"))
if externalmember is not None:
externalmember = gen_intersection_list(
externalmember, res_find.get("member_external"))
if any([user, group, service, externalmember]):
commands.append(
[name, "group_remove_member", del_member_args]
)
if has_add_membermanager:
# Reduce del lists of membermanager_user and
# membermanager_group to the entries only that are
# in res_find.
if membermanager_user is not None:
membermanager_user = gen_intersection_list(
membermanager_user,
res_find.get("membermanager_user"))
if membermanager_group is not None:
membermanager_group = gen_intersection_list(
membermanager_group,
res_find.get("membermanager_group"))
# Remove membermanager users and groups
if membermanager_user is not None or \
membermanager_group is not None:
commands.append(
[name, "group_remove_member_manager",
{
"user": membermanager_user,
"group": membermanager_group,
}]
)
user_del = gen_intersection_list(
user, res_find.get("member_user"))
group_del = gen_intersection_list(
group, res_find.get("member_group"))
service_del = gen_intersection_list(
service, res_find.get("member_service"))
externalmember_del = gen_intersection_list(
externalmember, res_find.get("member_external"))
idoverrides_del = gen_intersection_list(
idoverrideuser, res_find.get("member_idoverrideuser"))
membermanager_user_del = gen_intersection_list(
membermanager_user, res_find.get("membermanager_user"))
membermanager_group_del = gen_intersection_list(
membermanager_group,
res_find.get("membermanager_group")
)
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Execute commands
# manage members
# setup member args for add/remove members.
add_member_args = {
"user": user_add,
"group": group_add,
}
del_member_args = {
"user": user_del,
"group": group_del,
}
if has_idoverrideuser:
add_member_args["idoverrideuser"] = idoverrides_add
del_member_args["idoverrideuser"] = idoverrides_del
if has_add_member_service:
add_member_args["service"] = service_add
del_member_args["service"] = service_del
if is_external_group(res_find):
add_member_args["ipaexternalmember"] = \
externalmember_add
del_member_args["ipaexternalmember"] = \
externalmember_del
elif externalmember or external:
ansible_module.fail_json(
msg="Cannot add external members to a "
"non-external group."
)
# Add members
add_members = any([user_add, group_add, idoverrides_add,
service_add, externalmember_add])
if add_members:
commands.append(
[name, "group_add_member", add_member_args]
)
# Remove members
remove_members = any([user_del, group_del, idoverrides_del,
service_del, externalmember_del])
if remove_members:
commands.append(
[name, "group_remove_member", del_member_args]
)
# manage membermanager members
if has_add_membermanager:
# Add membermanager users and groups
if any([membermanager_user_add, membermanager_group_add]):
commands.append(
[name, "group_add_member_manager",
{
"user": membermanager_user_add,
"group": membermanager_group_add,
}]
)
# Remove member manager
if any([membermanager_user_del, membermanager_group_del]):
commands.append(
[name, "group_remove_member_manager",
{
"user": membermanager_user_del,
"group": membermanager_group_del,
}]
)
# Execute commands
changed = ansible_module.execute_ipa_commands(
commands, fail_on_member_errors=True)

View File

@@ -238,12 +238,12 @@ def main():
hostcategory = ansible_module.params_get("hostcategory")
servicecategory = ansible_module.params_get("servicecategory")
nomembers = ansible_module.params_get("nomembers")
host = ansible_module.params_get("host")
hostgroup = ansible_module.params_get("hostgroup")
hbacsvc = ansible_module.params_get("hbacsvc")
hbacsvcgroup = ansible_module.params_get("hbacsvcgroup")
user = ansible_module.params_get("user")
group = ansible_module.params_get("group")
host = ansible_module.params_get_lowercase("host")
hostgroup = ansible_module.params_get_lowercase("hostgroup")
hbacsvc = ansible_module.params_get_lowercase("hbacsvc")
hbacsvcgroup = ansible_module.params_get_lowercase("hbacsvcgroup")
user = ansible_module.params_get_lowercase("user")
group = ansible_module.params_get_lowercase("group")
action = ansible_module.params_get("action")
# state
state = ansible_module.params_get("state")
@@ -307,7 +307,7 @@ def main():
# Ensure fqdn host names, use default domain for simple names
if host is not None:
_host = [ensure_fqdn(x, default_domain) for x in host]
_host = [ensure_fqdn(x, default_domain).lower() for x in host]
host = _host
commands = []
@@ -316,6 +316,13 @@ def main():
# Make sure hbacrule exists
res_find = find_hbacrule(ansible_module, name)
host_add, host_del = [], []
hostgroup_add, hostgroup_del = [], []
hbacsvc_add, hbacsvc_del = [], []
hbacsvcgroup_add, hbacsvcgroup_del = [], []
user_add, user_del = [], []
group_add, group_del = [], []
# Create command
if state == "present":
# Generate args
@@ -353,69 +360,30 @@ def main():
res_find = {}
# Generate addition and removal lists
host_add, host_del = gen_add_del_lists(
host, res_find.get("memberhost_host"))
if host is not None:
host_add, host_del = gen_add_del_lists(
host, res_find.get("memberhost_host"))
hostgroup_add, hostgroup_del = gen_add_del_lists(
hostgroup, res_find.get("memberhost_hostgroup"))
if hostgroup is not None:
hostgroup_add, hostgroup_del = gen_add_del_lists(
hostgroup, res_find.get("memberhost_hostgroup"))
hbacsvc_add, hbacsvc_del = gen_add_del_lists(
hbacsvc, res_find.get("memberservice_hbacsvc"))
if hbacsvc is not None:
hbacsvc_add, hbacsvc_del = gen_add_del_lists(
hbacsvc, res_find.get("memberservice_hbacsvc"))
hbacsvcgroup_add, hbacsvcgroup_del = gen_add_del_lists(
hbacsvcgroup,
res_find.get("memberservice_hbacsvcgroup"))
if hbacsvcgroup is not None:
hbacsvcgroup_add, hbacsvcgroup_del = gen_add_del_lists(
hbacsvcgroup,
res_find.get("memberservice_hbacsvcgroup"))
user_add, user_del = gen_add_del_lists(
user, res_find.get("memberuser_user"))
if user is not None:
user_add, user_del = gen_add_del_lists(
user, res_find.get("memberuser_user"))
group_add, group_del = gen_add_del_lists(
group, res_find.get("memberuser_group"))
# Add hosts and hostgroups
if len(host_add) > 0 or len(hostgroup_add) > 0:
commands.append([name, "hbacrule_add_host",
{
"host": host_add,
"hostgroup": hostgroup_add,
}])
# Remove hosts and hostgroups
if len(host_del) > 0 or len(hostgroup_del) > 0:
commands.append([name, "hbacrule_remove_host",
{
"host": host_del,
"hostgroup": hostgroup_del,
}])
# Add hbacsvcs and hbacsvcgroups
if len(hbacsvc_add) > 0 or len(hbacsvcgroup_add) > 0:
commands.append([name, "hbacrule_add_service",
{
"hbacsvc": hbacsvc_add,
"hbacsvcgroup": hbacsvcgroup_add,
}])
# Remove hbacsvcs and hbacsvcgroups
if len(hbacsvc_del) > 0 or len(hbacsvcgroup_del) > 0:
commands.append([name, "hbacrule_remove_service",
{
"hbacsvc": hbacsvc_del,
"hbacsvcgroup": hbacsvcgroup_del,
}])
# Add users and groups
if len(user_add) > 0 or len(group_add) > 0:
commands.append([name, "hbacrule_add_user",
{
"user": user_add,
"group": group_add,
}])
# Remove users and groups
if len(user_del) > 0 or len(group_del) > 0:
commands.append([name, "hbacrule_remove_user",
{
"user": user_del,
"group": group_del,
}])
if group is not None:
group_add, group_del = gen_add_del_lists(
group, res_find.get("memberuser_group"))
elif action == "member":
if res_find is None:
@@ -424,63 +392,33 @@ def main():
# Generate add lists for host, hostgroup and
# res_find to only try to add hosts and hostgroups
# that not in hbacrule already
if host is not None and \
"memberhost_host" in res_find:
host = gen_add_list(
host, res_find["memberhost_host"])
if hostgroup is not None and \
"memberhost_hostgroup" in res_find:
hostgroup = gen_add_list(
hostgroup, res_find["memberhost_hostgroup"])
# Add hosts and hostgroups
if host is not None or hostgroup is not None:
commands.append([name, "hbacrule_add_host",
{
"host": host,
"hostgroup": hostgroup,
}])
if host:
host_add = gen_add_list(
host, res_find.get("memberhost_host"))
if hostgroup:
hostgroup_add = gen_add_list(
hostgroup, res_find.get("memberhost_hostgroup"))
# Generate add lists for hbacsvc, hbacsvcgroup and
# res_find to only try to add hbacsvcs and hbacsvcgroups
# that not in hbacrule already
if hbacsvc is not None and \
"memberservice_hbacsvc" in res_find:
hbacsvc = gen_add_list(
hbacsvc, res_find["memberservice_hbacsvc"])
if hbacsvcgroup is not None and \
"memberservice_hbacsvcgroup" in res_find:
hbacsvcgroup = gen_add_list(
if hbacsvc:
hbacsvc_add = gen_add_list(
hbacsvc, res_find.get("memberservice_hbacsvc"))
if hbacsvcgroup:
hbacsvcgroup_add = gen_add_list(
hbacsvcgroup,
res_find["memberservice_hbacsvcgroup"])
# Add hbacsvcs and hbacsvcgroups
if hbacsvc is not None or hbacsvcgroup is not None:
commands.append([name, "hbacrule_add_service",
{
"hbacsvc": hbacsvc,
"hbacsvcgroup": hbacsvcgroup,
}])
res_find.get("memberservice_hbacsvcgroup"))
# Generate add lists for user, group and
# res_find to only try to add users and groups
# that not in hbacrule already
if user is not None and \
"memberuser_user" in res_find:
user = gen_add_list(
user, res_find["memberuser_user"])
if group is not None and \
"memberuser_group" in res_find:
group = gen_add_list(
group, res_find["memberuser_group"])
# Add users and groups
if user is not None or group is not None:
commands.append([name, "hbacrule_add_user",
{
"user": user,
"group": group,
}])
if user:
user_add = gen_add_list(
user, res_find.get("memberuser_user"))
if group:
group_add = gen_add_list(
group, res_find.get("memberuser_group"))
elif state == "absent":
if action == "hbacrule":
@@ -494,75 +432,39 @@ def main():
# Generate intersection lists for host, hostgroup and
# res_find to only try to remove hosts and hostgroups
# that are in hbacrule
if host is not None:
if host:
if "memberhost_host" in res_find:
host = gen_intersection_list(
host_del = gen_intersection_list(
host, res_find["memberhost_host"])
else:
host = None
if hostgroup is not None:
if hostgroup:
if "memberhost_hostgroup" in res_find:
hostgroup = gen_intersection_list(
hostgroup_del = gen_intersection_list(
hostgroup, res_find["memberhost_hostgroup"])
else:
hostgroup = None
# Remove hosts and hostgroups
if host is not None or hostgroup is not None:
commands.append([name, "hbacrule_remove_host",
{
"host": host,
"hostgroup": hostgroup,
}])
# Generate intersection lists for hbacsvc, hbacsvcgroup
# and res_find to only try to remove hbacsvcs and
# hbacsvcgroups that are in hbacrule
if hbacsvc is not None:
if hbacsvc:
if "memberservice_hbacsvc" in res_find:
hbacsvc = gen_intersection_list(
hbacsvc_del = gen_intersection_list(
hbacsvc, res_find["memberservice_hbacsvc"])
else:
hbacsvc = None
if hbacsvcgroup is not None:
if hbacsvcgroup:
if "memberservice_hbacsvcgroup" in res_find:
hbacsvcgroup = gen_intersection_list(
hbacsvcgroup_del = gen_intersection_list(
hbacsvcgroup,
res_find["memberservice_hbacsvcgroup"])
else:
hbacsvcgroup = None
# Remove hbacsvcs and hbacsvcgroups
if hbacsvc is not None or hbacsvcgroup is not None:
commands.append([name, "hbacrule_remove_service",
{
"hbacsvc": hbacsvc,
"hbacsvcgroup": hbacsvcgroup,
}])
# Generate intersection lists for user, group and
# res_find to only try to remove users and groups
# that are in hbacrule
if user is not None:
if user:
if "memberuser_user" in res_find:
user = gen_intersection_list(
user_del = gen_intersection_list(
user, res_find["memberuser_user"])
else:
user = None
if group is not None:
if group:
if "memberuser_group" in res_find:
group = gen_intersection_list(
group_del = gen_intersection_list(
group, res_find["memberuser_group"])
else:
group = None
# Remove users and groups
if user is not None or group is not None:
commands.append([name, "hbacrule_remove_user",
{
"user": user,
"group": group,
}])
elif state == "enabled":
if res_find is None:
@@ -570,23 +472,78 @@ def main():
# hbacrule_enable is not failing on an enabled hbacrule
# Therefore it is needed to have a look at the ipaenabledflag
# in res_find.
if "ipaenabledflag" not in res_find or \
res_find["ipaenabledflag"][0] != "TRUE":
# FreeIPA 4.9.10+ and 4.10 use proper mapping for
# boolean values, so we need to convert it to str
# for comparison.
# See: https://github.com/freeipa/freeipa/pull/6294
enabled_flag = str(res_find.get("ipaenabledflag", [False])[0])
if enabled_flag.upper() != "TRUE":
commands.append([name, "hbacrule_enable", {}])
elif state == "disabled":
if res_find is None:
ansible_module.fail_json(msg="No hbacrule '%s'" % name)
# hbacrule_disable is not failing on an disabled hbacrule
# hbacrule_disable is not failing on an enabled hbacrule
# Therefore it is needed to have a look at the ipaenabledflag
# in res_find.
if "ipaenabledflag" not in res_find or \
res_find["ipaenabledflag"][0] != "FALSE":
# FreeIPA 4.9.10+ and 4.10 use proper mapping for
# boolean values, so we need to convert it to str
# for comparison.
# See: https://github.com/freeipa/freeipa/pull/6294
enabled_flag = str(res_find.get("ipaenabledflag", [False])[0])
if enabled_flag.upper() != "FALSE":
commands.append([name, "hbacrule_disable", {}])
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Manage HBAC rule members.
# Add hosts and hostgroups
if len(host_add) > 0 or len(hostgroup_add) > 0:
commands.append([name, "hbacrule_add_host",
{
"host": host_add,
"hostgroup": hostgroup_add,
}])
# Remove hosts and hostgroups
if len(host_del) > 0 or len(hostgroup_del) > 0:
commands.append([name, "hbacrule_remove_host",
{
"host": host_del,
"hostgroup": hostgroup_del,
}])
# Add hbacsvcs and hbacsvcgroups
if len(hbacsvc_add) > 0 or len(hbacsvcgroup_add) > 0:
commands.append([name, "hbacrule_add_service",
{
"hbacsvc": hbacsvc_add,
"hbacsvcgroup": hbacsvcgroup_add,
}])
# Remove hbacsvcs and hbacsvcgroups
if len(hbacsvc_del) > 0 or len(hbacsvcgroup_del) > 0:
commands.append([name, "hbacrule_remove_service",
{
"hbacsvc": hbacsvc_del,
"hbacsvcgroup": hbacsvcgroup_del,
}])
# Add users and groups
if len(user_add) > 0 or len(group_add) > 0:
commands.append([name, "hbacrule_add_user",
{
"user": user_add,
"group": group_add,
}])
# Remove users and groups
if len(user_del) > 0 or len(group_del) > 0:
commands.append([name, "hbacrule_remove_user",
{
"user": user_del,
"group": group_del,
}])
# Execute commands
changed = ansible_module.execute_ipa_commands(

View File

@@ -101,7 +101,8 @@ RETURN = """
"""
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, gen_add_list, \
gen_intersection_list
def find_hbacsvcgroup(module, name):
@@ -183,7 +184,7 @@ def main():
# present
description = ansible_module.params_get("description")
nomembers = ansible_module.params_get("nomembers")
hbacsvc = ansible_module.params_get("hbacsvc")
hbacsvc = ansible_module.params_get_lowercase("hbacsvc")
action = ansible_module.params_get("action")
# state
state = ansible_module.params_get("state")
@@ -223,6 +224,8 @@ def main():
# Make sure hbacsvcgroup exists
res_find = find_hbacsvcgroup(ansible_module, name)
hbacsvc_add, hbacsvc_del = [], []
# Create command
if state == "present":
# Generate args
@@ -246,32 +249,20 @@ def main():
if not compare_args_ipa(ansible_module, member_args,
res_find):
# Generate addition and removal lists
hbacsvc_add, hbacsvc_del = gen_add_del_lists(
hbacsvc, res_find.get("member_hbacsvc"))
if hbacsvc is not None:
hbacsvc_add, hbacsvc_del = gen_add_del_lists(
hbacsvc, res_find.get("member_hbacsvc"))
# Add members
if len(hbacsvc_add) > 0:
commands.append([name, "hbacsvcgroup_add_member",
{
"hbacsvc": hbacsvc_add
}])
# Remove members
if len(hbacsvc_del) > 0:
commands.append([name,
"hbacsvcgroup_remove_member",
{
"hbacsvc": hbacsvc_del
}])
elif action == "member":
if res_find is None:
ansible_module.fail_json(
msg="No hbacsvcgroup '%s'" % name)
# Ensure members are present
commands.append([name, "hbacsvcgroup_add_member",
{
"hbacsvc": hbacsvc
}])
if hbacsvc:
hbacsvc_add = gen_add_list(
hbacsvc, res_find.get("member_hbacsvc"))
elif state == "absent":
if action == "hbacsvcgroup":
if res_find is not None:
@@ -283,15 +274,28 @@ def main():
msg="No hbacsvcgroup '%s'" % name)
# Ensure members are absent
commands.append([name, "hbacsvcgroup_remove_member",
{
"hbacsvc": hbacsvc
}])
if hbacsvc:
hbacsvc_del = gen_intersection_list(
hbacsvc, res_find.get("member_hbacsvc"))
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Execute commands
# Manage members
if len(hbacsvc_add) > 0:
commands.append([name, "hbacsvcgroup_add_member",
{
"hbacsvc": hbacsvc_add
}])
# Remove members
if len(hbacsvc_del) > 0:
commands.append([name,
"hbacsvcgroup_remove_member",
{
"hbacsvc": hbacsvc_del
}])
# Execute commands
changed = ansible_module.execute_ipa_commands(commands, result_handler)
# Done

View File

@@ -709,7 +709,7 @@ def main():
elements='dict', required=False),
# mod
update_password=dict(type='str', default=None,
update_password=dict(type='str', default=None, no_log=False,
choices=['always', 'on_create']),
# general
@@ -764,7 +764,7 @@ def main():
mac_address = ansible_module.params_get("mac_address")
sshpubkey = ansible_module.params_get("sshpubkey")
userclass = ansible_module.params_get("userclass")
auth_ind = ansible_module.params_get("auth_ind")
auth_ind = ansible_module.params_get("auth_ind", allow_empty_string=True)
requires_pre_auth = ansible_module.params_get("requires_pre_auth")
ok_as_delegate = ansible_module.params_get("ok_as_delegate")
ok_to_auth_as_delegate = ansible_module.params_get(

View File

@@ -139,7 +139,7 @@ RETURN = """
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, gen_add_list, \
gen_intersection_list
gen_intersection_list, ensure_fqdn
def find_hostgroup(module, name):
@@ -281,6 +281,15 @@ def main():
ansible_module.fail_json(
msg="Renaming hostgroups is not supported by your IPA version")
# If hosts are given, ensure that the hosts are FQDN and also
# lowercase to be able to do a proper comparison to exising hosts
# in the hostgroup.
# Fixes #666 (ipahostgroup not idempotent and with error)
if host is not None:
default_domain = ansible_module.ipa_get_domain()
host = [ensure_fqdn(_host, default_domain).lower()
for _host in host]
commands = []
for name in names:

View File

@@ -0,0 +1,340 @@
# -*- coding: utf-8 -*-
# Authors:
# Rafael Guterres Jeffman <rjeffman@redhat.com>
#
# Copyright (C) 2022 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: ipaidrange
short description: Manage FreeIPA idrange
description: Manage FreeIPA idrange
extends_documentation_fragment:
- ipamodule_base_docs
- ipamodule_base_docs.delete_continue
options:
name:
description: The list of idrange name strings.
required: true
aliases: ["cn"]
base_id:
description: First Posix ID of the range.
type: int
required: false
aliases: ["ipabaseid"]
range_size:
description: Number of IDs in the range.
type: int
required: false
aliases: ["ipaidrangesize"]
rid_base:
description: First RID of the corresponding RID range.
type: int
required: false
aliases: ["ipabaserid"]
secondary_rid_base:
description: First RID of the secondary RID range.
type: int
required: false
aliases: ["ipasecondarybaserid"]
idrange_type:
description: ID range type.
type: string
required: false
choices: ["ipa-ad-trust", "ipa-ad-trust-posix", "ipa-local"]
aliases: ["iparangetype"]
dom_sid:
description: Domain SID of the trusted domain.
type: string
required: false
aliases: ["ipanttrusteddomainsid"]
dom_name:
description: |
Domain name of the trusted domain. Can only be used when
`ipaapi_context: server`.
type: string
required: false
aliases: ["ipanttrusteddomainname"]
auto_private_groups:
description: Auto creation of private groups.
type: string
required: false
choices: ["true", "false", "hybrid"]
aliases: ["ipaautoprivategroups"]
state:
description: The state to ensure.
choices: ["present", "absent"]
default: present
required: true
"""
EXAMPLES = """
# Ensure local domain idrange is present
- ipaidrange:
ipaadmin_password: SomeADMINpassword
name: id_range
base_id: 150000000
range_size: 200000
rid_base: 1000000
secondary_rid_base: 200000000
# Ensure local domain idrange is absent
- ipaidrange:
ipaadmin_password: SomeADMINpassword
name: id_range
state: absent
# Ensure AD-trust idrange is present
- ipaidrange:
name: id_range
base_id: 150000000
range_size: 200000
rid_base: 1000000
idrange_type: ipa-ad-trust
dom_sid: S-1-5-21-2870384104-3340008087-3140804251
auto_private_groups: "false"
# Ensure AD-trust idrange is present, with range type ad-trust-posix,
# and using domain name
- ipaidrange:
name: id_range
base_id: 150000000
range_size: 200000
rid_base: 1000000
idrange_type: ipa-ad-trust-posix
dom_name: ad.ipa.test
auto_private_groups: "hybrid"
"""
RETURN = """
"""
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, get_trusted_domain_sid_from_name
from ansible.module_utils import six
if six.PY3:
unicode = str
def find_idrange(module, name):
"""Find if a idrange with the given name already exist."""
try:
_result = module.ipa_command("idrange_show", name, {"all": True})
except Exception: # pylint: disable=broad-except
# An exception is raised if idrange name is not found.
return None
else:
return _result["result"]
def gen_args(
base_id, range_size, rid_base, secondary_rid_base, idrange_type, dom_sid,
dom_name, auto_private_groups
):
_args = {}
# Integer parameters are stored as strings.
# Converting them here allows the proper use of compare_args_ipa.
if base_id is not None:
_args["ipabaseid"] = base_id
if range_size is not None:
_args["ipaidrangesize"] = range_size
if rid_base is not None:
_args["ipabaserid"] = rid_base
if secondary_rid_base is not None:
_args["ipasecondarybaserid"] = secondary_rid_base
if idrange_type is not None:
_args["iparangetype"] = idrange_type
if dom_name is not None:
dom_sid = get_trusted_domain_sid_from_name(dom_name)
if dom_sid is not None:
_args["ipanttrusteddomainsid"] = dom_sid
if auto_private_groups is not None:
_args["ipaautoprivategroups"] = auto_private_groups
return _args
def main():
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# general
name=dict(type="list", aliases=["cn"],
default=None, required=True),
# present
base_id=dict(required=False, type='int',
aliases=["ipabaseid"], default=None),
range_size=dict(required=False, type='int',
aliases=["ipaidrangesize"], default=None),
rid_base=dict(required=False, type='int',
aliases=["ipabaserid"], default=None),
secondary_rid_base=dict(required=False, type='int', default=None,
aliases=["ipasecondarybaserid"]),
idrange_type=dict(required=False, aliases=["iparangetype"],
type="str", default=None,
choices=["ipa-ad-trust", "ipa-ad-trust-posix",
"ipa-local"]),
dom_sid=dict(required=False, type='str', default=None,
aliases=["ipanttrusteddomainsid"]),
dom_name=dict(required=False, type='str', default=None,
aliases=["ipanttrusteddomainname"]),
auto_private_groups=dict(required=False, type='str', default=None,
aliases=["ipaautoprivategroups"],
choices=['true', 'false', 'hybrid']),
# state
state=dict(type="str", default="present",
choices=["present", "absent"]),
),
mutually_exclusive=[
["dom_sid", "secondary_rid_base"],
["dom_name", "secondary_rid_base"],
["dom_sid", "dom_name"],
],
supports_check_mode=True,
ipa_module_options=["delete_continue"],
)
ansible_module._ansible_debug = True
# Get parameters
# general
names = ansible_module.params_get("name")
delete_continue = ansible_module.params_get("delete_continue")
# present
base_id = ansible_module.params_get("base_id")
range_size = ansible_module.params_get("range_size")
rid_base = ansible_module.params_get("rid_base")
secondary_rid_base = ansible_module.params_get("secondary_rid_base")
idrange_type = ansible_module.params_get("idrange_type")
dom_sid = ansible_module.params_get("dom_sid")
dom_name = ansible_module.params_get("dom_name")
auto_private_groups = \
ansible_module.params_get_lowercase("auto_private_groups")
# state
state = ansible_module.params_get("state")
# Check parameters
invalid = []
if state == "present":
if len(names) != 1:
ansible_module.fail_json(
msg="Only one idrange can be added at a time.")
if state == "absent":
if len(names) < 1:
ansible_module.fail_json(msg="No name given.")
invalid = [
"base_id", "range_size", "idrange_type", "dom_sid", "dom_name",
"rid_base", "secondary_rid_base", "auto_private_groups"
]
ansible_module.params_fail_used_invalid(invalid, state)
# Init
changed = False
exit_args = {}
range_types = {
"Active Directory domain range": "ipa-ad-trust",
"Active Directory trust range with POSIX attributes":
"ipa-ad-trust-posix",
"local domain range": "ipa-local",
}
# Connect to IPA API
with ansible_module.ipa_connect():
commands = []
for name in names:
# Make sure idrange exists
res_find = find_idrange(ansible_module, name)
# Create command
if state == "present":
# Generate args
args = gen_args(
base_id, range_size, rid_base, secondary_rid_base,
idrange_type, dom_sid, dom_name, auto_private_groups
)
# Found the idrange
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, ignore=["iparangetype"]
):
res_type = range_types.get(
res_find.get("iparangetype")[0]
)
if res_type == "local_id_range":
ansible_module.fail_json(
"Cannot modify local IPA domain idrange."
)
arg_type = args.get("iparangetype")
if arg_type:
if arg_type != res_type:
ansible_module.fail_json(
"Cannot modify idrange type."
)
del args["iparangetype"]
commands.append([name, "idrange_mod", args])
else:
commands.append([name, "idrange_add", args])
elif state == "absent":
if res_find is not None:
commands.append([
name,
"idrange_del",
{"continue": delete_continue or False}
])
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

@@ -72,7 +72,7 @@ options:
required: false
state:
description: The state to ensure.
choices: ["present", "absent"]
choices: ["present", "absent", "renamed"]
default: present
required: true
"""
@@ -103,10 +103,10 @@ EXAMPLES = """
# pylint: disable=no-name-in-module
from ansible.module_utils._text import to_text
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, gen_add_del_lists, compare_args_ipa
IPAAnsibleModule, gen_add_del_lists, compare_args_ipa, \
gen_intersection_list, ensure_fqdn
from ansible.module_utils import six
if six.PY3:
unicode = str
@@ -145,9 +145,22 @@ def check_parameters(module):
invalid = []
if state == "present":
if state == "renamed":
if action == "member":
invalid.extend(['description', 'rename'])
module.fail_json(
msg="Invalid action 'member' with state 'renamed'.")
invalid = [
"description",
"user", "group",
"host", "hostgroup",
"service",
"privilege",
]
if state == "present":
invalid = ["rename"]
if action == "member":
invalid.extend(['description'])
if state == "absent":
invalid.extend(['description', 'rename'])
@@ -157,28 +170,15 @@ def check_parameters(module):
module.params_fail_used_invalid(invalid, state, action)
def member_intersect(module, attr, memberof, res_find):
"""Filter member arguments from role found by intersection."""
params = module.params_get(attr)
if not res_find:
return params
filtered = []
if params:
existing = res_find.get(memberof, [])
filtered = list(set(params) & set(existing))
return filtered
def member_difference(module, attr, memberof, res_find):
"""Filter member arguments from role found by difference."""
params = module.params_get(attr)
if not res_find:
return params
filtered = []
if params:
existing = res_find.get(memberof, [])
filtered = list(set(params) - set(existing))
return filtered
def get_member_host_with_fqdn_lowercase(module, mod_member):
"""Retrieve host members from module, as FQDN, lowercase."""
default_domain = module.ipa_get_domain()
hosts = module.params_get(mod_member)
return (
[ensure_fqdn(host, default_domain).lower() for host in hosts]
if hosts
else hosts
)
def ensure_absent_state(module, name, action, res_find):
@@ -190,23 +190,41 @@ def ensure_absent_state(module, name, action, res_find):
if action == "member":
members = member_intersect(
module, 'privilege', 'memberof_privilege', res_find)
if members:
commands.append([name, "role_remove_privilege",
{"privilege": members}])
_members = module.params_get_lowercase("privilege")
if _members is not None:
del_list = gen_intersection_list(
_members,
result_get_value_lowercase(res_find, "memberof_privilege")
)
if del_list:
commands.append([name, "role_remove_privilege",
{"privilege": del_list}])
member_args = {}
for key in ['user', 'group', 'host', 'hostgroup']:
items = member_intersect(
module, key, 'member_%s' % key, res_find)
if items:
member_args[key] = items
for key in ['user', 'group', 'hostgroup']:
_members = module.params_get_lowercase(key)
if _members:
del_list = gen_intersection_list(
_members,
result_get_value_lowercase(res_find, "member_%s" % key)
)
if del_list:
member_args[key] = del_list
_services = filter_service(module, res_find,
lambda res, svc: res.startswith(svc))
# ensure hosts are FQDN.
_members = get_member_host_with_fqdn_lowercase(module, "host")
if _members:
del_list = gen_intersection_list(
_members, res_find.get('member_host'))
if del_list:
member_args["host"] = del_list
_services = get_service_param(module, "service")
if _services:
member_args['service'] = _services
_existing = result_get_value_lowercase(res_find, "member_service")
items = gen_intersection_list(_services.keys(), _existing)
if items:
member_args["service"] = [_services[key] for key in items]
# Only add remove command if there's at least one member no manage.
if member_args:
@@ -215,66 +233,112 @@ def ensure_absent_state(module, name, action, res_find):
return commands
def filter_service(module, res_find, predicate):
def get_service_param(module, key):
"""
Filter service based on predicate.
Retrieve dict of services, with realm, from the module parameters.
Compare service name with existing ones matching
at least until `@` from principal name.
Predicate is a callable that accepts the existing service, and the
modified service to be compared to.
As the services are compared in a case insensitive manner, but
are recorded in a case preserving way, a dict mapping the services
in lowercase to the provided module parameter is generated, so
that dict keys can be used for comparison and the values are used
with IPA API.
"""
_services = []
service = module.params_get('service')
if service:
existing = [to_text(x) for x in res_find.get('member_service', [])]
for svc in service:
svc = svc if '@' in svc else ('%s@' % svc)
found = [x for x in existing if predicate(x, svc)]
_services.extend(found)
_services = module.params_get(key)
if _services is not None:
ipa_realm = module.ipa_get_realm()
_services = [
to_text(svc) if '@' in svc else ('%s@%s' % (svc, ipa_realm))
for svc in _services
]
if _services:
_services = {svc.lower(): svc for svc in _services}
return _services
def result_get_value_lowercase(res_find, key, default=None):
"""
Retrieve a member of a dictionary converted to lowercase.
If field data is a string it is returned in lowercase. If
field data is a list or tuple, it is assumed that all values
are strings and the result is a list of strings in lowercase.
If 'key' is not found in the dictionary, returns 'default'.
"""
existing = res_find.get(key)
if existing is not None:
if isinstance(existing, (list, tuple)):
existing = [to_text(item).lower() for item in existing]
if isinstance(existing, (str, unicode)):
existing = existing.lower()
else:
existing = default
return existing
def gen_services_add_del_lists(module, mod_member, res_find, res_member):
"""Generate add/del lists for service principals."""
add_list, del_list = None, None
_services = get_service_param(module, mod_member)
if _services is not None:
_existing = result_get_value_lowercase(res_find, res_member)
add_list, del_list = gen_add_del_lists(_services.keys(), _existing)
if add_list:
add_list = [_services[key] for key in add_list]
if del_list:
del_list = [to_text(item) for item in del_list]
return add_list, del_list
def ensure_role_with_members_is_present(module, name, res_find, action):
"""Define commands to ensure member are present for action `role`."""
commands = []
privilege_add, privilege_del = gen_add_del_lists(
module.params_get("privilege"),
res_find.get('memberof_privilege', []))
if privilege_add:
commands.append([name, "role_add_privilege",
{"privilege": privilege_add}])
if action == "role" and privilege_del:
commands.append([name, "role_remove_privilege",
{"privilege": privilege_del}])
_members = module.params_get_lowercase("privilege")
if _members:
add_list, del_list = gen_add_del_lists(
_members,
result_get_value_lowercase(res_find, "memberof_privilege")
)
if add_list:
commands.append([name, "role_add_privilege",
{"privilege": add_list}])
if action == "role" and del_list:
commands.append([name, "role_remove_privilege",
{"privilege": del_list}])
add_members = {}
del_members = {}
for key in ["user", "group", "host", "hostgroup"]:
add_list, del_list = gen_add_del_lists(
module.params_get(key),
res_find.get('member_%s' % key, [])
)
if add_list:
add_members[key] = add_list
if del_list:
del_members[key] = [to_text(item) for item in del_list]
for key in ["user", "group", "hostgroup"]:
_members = module.params_get_lowercase(key)
if _members is not None:
add_list, del_list = gen_add_del_lists(
_members,
result_get_value_lowercase(res_find, "member_%s" % key)
)
if add_list:
add_members[key] = add_list
if del_list:
del_members[key] = del_list
service = [
to_text(svc)
if '@' in svc
else ('%s@%s' % (svc, module.ipa_get_realm()))
for svc in (module.params_get('service') or [])
]
existing = [str(svc) for svc in res_find.get('member_service', [])]
add_list, del_list = gen_add_del_lists(service, existing)
if add_list:
add_members['service'] = add_list
if del_list:
del_members['service'] = [to_text(item) for item in del_list]
# ensure hosts are FQDN.
_members = get_member_host_with_fqdn_lowercase(module, "host")
if _members:
add_list, del_list = gen_add_del_lists(
_members, res_find.get('member_host'))
if add_list:
add_members["host"] = add_list
if del_list:
del_members["host"] = del_list
(add_services, del_services) = gen_services_add_del_lists(
module, "service", res_find, "member_service")
if add_services:
add_members["service"] = add_services
if del_services:
del_members["service"] = del_services
if add_members:
commands.append([name, "role_add_member", add_members])
@@ -285,67 +349,24 @@ def ensure_role_with_members_is_present(module, name, res_find, action):
return commands
def ensure_members_are_present(module, name, res_find):
"""Define commands to ensure members are present for action `member`."""
commands = []
members = member_difference(
module, 'privilege', 'memberof_privilege', res_find)
if members:
commands.append([name, "role_add_privilege",
{"privilege": members}])
member_args = {}
for key in ['user', 'group', 'host', 'hostgroup']:
items = member_difference(
module, key, 'member_%s' % key, res_find)
if items:
member_args[key] = items
_services = filter_service(module, res_find,
lambda res, svc: not res.startswith(svc))
if _services:
member_args['service'] = _services
if member_args:
commands.append([name, "role_add_member", member_args])
return commands
# pylint: disable=unused-argument
def result_handler(module, result, command, name, args, errors):
"""Process the result of a command, looking for 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 len(result["failed"]) > 0:
for item in result["failed"]:
failed_item = result["failed"][item]
for member_type in failed_item:
for member, failure in failed_item[member_type]:
if "already a member" in failure \
or "not a member" in failure:
continue
errors.append("%s: %s %s: %s" % (
command, member_type, member, failure))
def role_commands_for_name(module, state, action, name):
"""Define commands for the Role module."""
commands = []
rename = module.params_get("rename")
res_find = find_role(module, name)
if state == "renamed":
args = gen_args(module)
if res_find is None:
module.fail_json(msg="No role '%s'" % name)
else:
commands.append([name, 'role_mod', args])
if state == "present":
args = gen_args(module)
if action == "role":
if res_find is None:
if rename is not None:
module.fail_json(msg="Cannot `rename` inexistent role.")
commands.append([name, 'role_add', args])
res_find = {}
else:
@@ -391,7 +412,7 @@ def create_module():
action=dict(type="str", default="role",
choices=["role", "member"]),
state=dict(type="str", default="present",
choices=["present", "absent"]),
choices=["present", "absent", "renamed"]),
),
supports_check_mode=True,
mutually_exclusive=[],
@@ -426,7 +447,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
ansible_module.exit_json(changed=changed, **exit_args)

View File

@@ -50,13 +50,13 @@ options:
pac_type:
description: Supported PAC type.
required: false
choices: ["MS-PAC", "PAD", "NONE"]
choices: ["MS-PAC", "PAD", "NONE", ""]
type: list
aliases: ["pac_type", "ipakrbauthzdata"]
auth_ind:
description: Defines a whitelist for Authentication Indicators.
required: false
choices: ["otp", "radius", "pkinit", "hardened"]
choices: ["otp", "radius", "pkinit", "hardened", ""]
aliases: ["krbprincipalauthind"]
skip_host_check:
description: Skip checking if host object exists.
@@ -356,7 +356,7 @@ def init_ansible_module():
smb=dict(type="bool", required=False),
netbiosname=dict(type="str", required=False),
pac_type=dict(type="list", aliases=["ipakrbauthzdata"],
choices=["MS-PAC", "PAD", "NONE"]),
choices=["MS-PAC", "PAD", "NONE", ""]),
auth_ind=dict(type="list",
aliases=["krbprincipalauthind"],
choices=["otp", "radius", "pkinit", "hardened", ""]),
@@ -420,8 +420,8 @@ def main():
# service attributes
principal = ansible_module.params_get("principal")
certificate = ansible_module.params_get("certificate")
pac_type = ansible_module.params_get("pac_type")
auth_ind = ansible_module.params_get("auth_ind")
pac_type = ansible_module.params_get("pac_type", allow_empty_string=True)
auth_ind = ansible_module.params_get("auth_ind", allow_empty_string=True)
skip_host_check = ansible_module.params_get("skip_host_check")
force = ansible_module.params_get("force")
requires_pre_auth = ansible_module.params_get("requires_pre_auth")
@@ -537,6 +537,15 @@ def main():
if remove in args:
del args[remove]
if (
"ipakrbauthzdata" in args
and (
args.get("ipakrbauthzdata", [""]) ==
res_find.get("ipakrbauthzdata", [""])
)
):
del args["ipakrbauthzdata"]
if (
"krbprincipalauthind" in args
and (

View File

@@ -0,0 +1,352 @@
# -*- coding: utf-8 -*-
# Authors:
# Thomas Woerner <twoerner@redhat.com>
#
# Copyright (C) 2022 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: ipaservicedelegationrule
short description: Manage FreeIPA servicedelegationrule
description: |
Manage FreeIPA servicedelegationrule and servicedelegationrule members
extends_documentation_fragment:
- ipamodule_base_docs
options:
name:
description: The list of servicedelegationrule name strings.
required: true
aliases: ["cn"]
principal:
description: |
The list of principals. A principal can be of the format:
fqdn, fqdn@REALM, service/fqdn, service/fqdn@REALM, host/fqdn,
host/fqdn@REALM, alias$, alias$@REALM, where fqdn and fqdn@REALM
are host principals and the same as host/fqdn and host/fqd
Host princpals are only usable with IPA versions 4.9.0 and up.
required: false
target:
description: |
The list of service delegation targets.
required: false
aliases: ["servicedelegationtarget"]
action:
description: Work on servicedelegationrule or member level.
choices: ["servicedelegationrule", "member"]
default: servicedelegationrule
required: false
state:
description: The state to ensure.
choices: ["present", "absent"]
default: present
required: true
"""
EXAMPLES = """
# Ensure servicedelegationrule delegation-rule is present
- ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: delegation-rule
# Ensure servicedelegationrule delegation-rule member principal
# test/example.com is present
- ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: delegation-rule
principal: test/example.com
action: member
# Ensure servicedelegationrule delegation-rule member principal
# test/example.com is absent
- ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: delegation-rule
principal: test/example.com
action: member
state: absent
# Ensure servicedelegationrule delegation-rule member target
# test/example.com is present
- ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: delegation-rule
target: delegation-target
action: member
# Ensure servicedelegationrule delegation-rule member target
# test/example.com is absent
- ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: delegation-rule
target: delegation-target
action: member
state: absent
# Ensure servicedelegationrule delegation-rule is absent
- ipaservicedelegationrule:
ipaadmin_password: SomeADMINpassword
name: delegation-rule
state: absent
"""
RETURN = """
"""
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, gen_add_del_lists, gen_add_list, gen_intersection_list, \
servicedelegation_normalize_principals, ipalib_errors
from ansible.module_utils import six
if six.PY3:
unicode = str
def find_servicedelegationrule(module, name):
"""Find if a servicedelegationrule with the given name already exist."""
try:
_result = module.ipa_command("servicedelegationrule_show", name,
{"all": True})
except Exception: # pylint: disable=broad-except
# An exception is raised if servicedelegationrule name is not found.
return None
else:
return _result["result"]
def check_targets(module, targets):
def _check_exists(module, _type, name):
# Check if item of type _type exists using the show command
try:
module.ipa_command("%s_show" % _type, name, {})
except ipalib_errors.NotFound as e:
msg = str(e)
if "%s not found" % _type in msg:
return False
module.fail_json(msg="%s_show failed: %s" % (_type, msg))
return True
for _target in targets:
if not _check_exists(module, "servicedelegationtarget", _target):
module.fail_json(
msg="Service delegation target '%s' does not exist" % _target)
def main():
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# general
name=dict(type="list", aliases=["cn"], default=None,
required=True),
# present
principal=dict(required=False, type='list', default=None),
target=dict(required=False, type='list',
aliases=["servicedelegationtarget"], default=None),
action=dict(type="str", default="servicedelegationrule",
choices=["member", "servicedelegationrule"]),
# state
state=dict(type="str", default="present",
choices=["present", "absent"]),
),
supports_check_mode=True,
)
ansible_module._ansible_debug = True
# Get parameters
# general
names = ansible_module.params_get("name")
# present
principal = ansible_module.params_get("principal")
target = ansible_module.params_get("target")
action = ansible_module.params_get("action")
# state
state = ansible_module.params_get("state")
# Check parameters
invalid = []
if state == "present":
if len(names) != 1:
ansible_module.fail_json(
msg="Only one servicedelegationrule can be added at a time.")
if state == "absent":
if len(names) < 1:
ansible_module.fail_json(msg="No name given.")
if action == "servicedelegationrule":
invalid = ["principal", "target"]
ansible_module.params_fail_used_invalid(invalid, state, action)
# Init
membertarget = "ipaallowedtarget_servicedelegationtarget"
changed = False
exit_args = {}
# Connect to IPA API
with ansible_module.ipa_connect():
# Normalize principals
if principal:
principal = servicedelegation_normalize_principals(
ansible_module, principal, state == "present")
if target and state == "present":
check_targets(ansible_module, target)
commands = []
principal_add = principal_del = []
target_add = target_del = []
for name in names:
# Make sure servicedelegationrule exists
res_find = find_servicedelegationrule(ansible_module, name)
# Create command
if state == "present":
if action == "servicedelegationrule":
# A servicedelegationrule does not have normal options.
# There is no servicedelegationtarget-mod command.
# Principal members are handled with the _add_member and
# _remove_member commands further down.
if res_find is None:
commands.append([name, "servicedelegationrule_add",
{}])
res_find = {}
# Generate addition and removal lists for principal
principal_add, principal_del = gen_add_del_lists(
principal, res_find.get("memberprincipal"))
# Generate addition and removal lists for target
target_add, target_del = gen_add_del_lists(
target, res_find.get(membertarget))
elif action == "member":
if res_find is None:
ansible_module.fail_json(
msg="No servicedelegationrule '%s'" % name)
# Reduce add lists for principal
# to new entries only that are not in res_find.
if principal is not None and \
"memberprincipal" in res_find:
principal_add = gen_add_list(
principal, res_find["memberprincipal"])
else:
principal_add = principal
# Reduce add lists for target
# to new entries only that are not in res_find.
if target is not None and membertarget in res_find:
target_add = gen_add_list(
target, res_find[membertarget])
else:
target_add = target
elif state == "absent":
if action == "servicedelegationrule":
if res_find is not None:
commands.append([name, "servicedelegationrule_del",
{}])
elif action == "member":
if res_find is None:
ansible_module.fail_json(
msg="No servicedelegationrule '%s'" % name)
# Reduce del lists of principals to the entries only
# that are in res_find.
if principal is not None:
principal_del = gen_intersection_list(
principal, res_find.get("memberprincipal"))
else:
principal_del = principal
# Reduce del lists of targets to the entries only
# that are in res_find.
if target is not None:
target_del = gen_intersection_list(
target, res_find.get(membertarget))
else:
target_del = target
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Handle members
# Add principal members
if principal_add is not None and len(principal_add) > 0:
commands.append(
[name, "servicedelegationtarget_add_member",
{
"principal": principal_add,
}])
# Remove principal members
if principal_del is not None and len(principal_del) > 0:
commands.append(
[name, "servicedelegationtarget_remove_member",
{
"principal": principal_del,
}])
# Add target members
if target_add is not None and len(target_add) > 0:
commands.append(
[name, "servicedelegationrule_add_target",
{
"servicedelegationtarget": target_add,
}])
# Remove target members
if target_del is not None and len(target_del) > 0:
commands.append(
[name, "servicedelegationrule_remove_target",
{
"servicedelegationtarget": target_del,
}])
# Execute commands
changed = ansible_module.execute_ipa_commands(
commands, fail_on_member_errors=True)
# Done
ansible_module.exit_json(changed=changed, **exit_args)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,270 @@
# -*- coding: utf-8 -*-
# Authors:
# Thomas Woerner <twoerner@redhat.com>
#
# Copyright (C) 2022 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: ipaservicedelegationtarget
short description: Manage FreeIPA servicedelegationtarget
description: |
Manage FreeIPA servicedelegationtarget and servicedelegationtarget members
extends_documentation_fragment:
- ipamodule_base_docs
options:
name:
description: The list of servicedelegationtarget name strings.
required: true
aliases: ["cn"]
principal:
description: |
The list of principals. A principal can be of the format:
fqdn, fqdn@REALM, service/fqdn, service/fqdn@REALM, host/fqdn,
host/fqdn@REALM, alias$, alias$@REALM, where fqdn and fqdn@REALM
are host principals and the same as host/fqdn and host/fqdn@REALM.
Host princpals are only usable with IPA versions 4.9.0 and up.
required: false
action:
description: Work on servicedelegationtarget or member level.
choices: ["servicedelegationtarget", "member"]
default: servicedelegationtarget
required: false
state:
description: The state to ensure.
choices: ["present", "absent"]
default: present
required: true
"""
EXAMPLES = """
# Ensure servicedelegationtarget delegation-target is present
- ipaservicedelegationtarget:
ipaadmin_password: SomeADMINpassword
name: delegation-target
# Ensure servicedelegationtarget delegation-target member principal
# test/example.com is present
- ipaservicedelegationtarget:
ipaadmin_password: SomeADMINpassword
name: delegation-target
principal: test/example.com
action: member
# Ensure servicedelegationtarget delegation-target member principal
# test/example.com is absent
- ipaservicedelegationtarget:
ipaadmin_password: SomeADMINpassword
name: delegation-target
principal: test/example.com
action: member
state: absent
# Ensure servicedelegationtarget delegation-target is absent
- ipaservicedelegationtarget:
ipaadmin_password: SomeADMINpassword
name: delegation-target
state: absent
"""
RETURN = """
"""
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, gen_add_del_lists, gen_add_list, gen_intersection_list, \
servicedelegation_normalize_principals
from ansible.module_utils import six
if six.PY3:
unicode = str
def find_servicedelegationtarget(module, name):
"""Find if a servicedelegationtarget with the given name already exist."""
try:
_result = module.ipa_command("servicedelegationtarget_show", name,
{"all": True})
except Exception: # pylint: disable=broad-except
# An exception is raised if servicedelegationtarget name is not found.
return None
else:
return _result["result"]
def main():
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# general
name=dict(type="list", aliases=["cn"], default=None,
required=True),
# present
principal=dict(required=False, type='list', default=None),
action=dict(type="str", default="servicedelegationtarget",
choices=["member", "servicedelegationtarget"]),
# state
state=dict(type="str", default="present",
choices=["present", "absent"]),
),
supports_check_mode=True,
)
ansible_module._ansible_debug = True
# Get parameters
# general
names = ansible_module.params_get("name")
# present
principal = ansible_module.params_get("principal")
action = ansible_module.params_get("action")
# state
state = ansible_module.params_get("state")
# Check parameters
invalid = []
if state == "present":
if len(names) != 1:
ansible_module.fail_json(
msg="Only one servicedelegationtarget can be added at a time.")
if state == "absent":
if len(names) < 1:
ansible_module.fail_json(msg="No name given.")
if action == "servicedelegationtarget":
invalid.append("principal")
ansible_module.params_fail_used_invalid(invalid, state, action)
# Init
changed = False
exit_args = {}
# Connect to IPA API
with ansible_module.ipa_connect():
# Normalize principals
if principal:
principal = servicedelegation_normalize_principals(
ansible_module, principal, state == "present")
commands = []
principal_add = principal_del = []
for name in names:
# Make sure servicedelegationtarget exists
res_find = find_servicedelegationtarget(ansible_module, name)
# Create command
if state == "present":
if action == "servicedelegationtarget":
# A servicedelegationtarget does not have normal options.
# There is no servicedelegationtarget-mod command.
# Principal members are handled with the _add_member and
# _remove_member commands further down.
if res_find is None:
commands.append([name, "servicedelegationtarget_add",
{}])
res_find = {}
# Generate addition and removal lists
principal_add, principal_del = gen_add_del_lists(
principal, res_find.get("memberprincipal"))
elif action == "member":
if res_find is None:
ansible_module.fail_json(
msg="No servicedelegationtarget '%s'" % name)
# Reduce add lists for principal
# to new entries only that are not in res_find.
if principal is not None and \
"memberprincipal" in res_find:
principal_add = gen_add_list(
principal, res_find["memberprincipal"])
else:
principal_add = principal
elif state == "absent":
if action == "servicedelegationtarget":
if res_find is not None:
commands.append([name, "servicedelegationtarget_del",
{}])
elif action == "member":
if res_find is None:
ansible_module.fail_json(
msg="No servicedelegationtarget '%s'" % name)
# Reduce del lists of principal
# to the entries only that are in res_find.
if principal is not None:
principal_del = gen_intersection_list(
principal, res_find.get("memberprincipal"))
else:
principal_del = principal
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Handle members
# Add principal members
if principal_add is not None and len(principal_add) > 0:
commands.append(
[name, "servicedelegationtarget_add_member",
{
"principal": principal_add,
}])
# Remove principal members
if principal_del is not None and len(principal_del) > 0:
commands.append(
[name, "servicedelegationtarget_remove_member",
{
"principal": principal_del,
}])
# Execute commands
changed = ansible_module.execute_ipa_commands(
commands, fail_on_member_errors=True)
# Done
ansible_module.exit_json(changed=changed, **exit_args)
if __name__ == "__main__":
main()

View File

@@ -188,7 +188,7 @@ RETURN = """
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, gen_add_list, \
gen_intersection_list
gen_intersection_list, api_get_domain, ensure_fqdn
def find_sudorule(module, name):
@@ -297,17 +297,19 @@ def main():
hostcategory = ansible_module.params_get("hostcategory") # noqa
nomembers = ansible_module.params_get("nomembers") # noqa
host = ansible_module.params_get("host")
hostgroup = ansible_module.params_get("hostgroup")
user = ansible_module.params_get("user")
group = ansible_module.params_get("group")
hostgroup = ansible_module.params_get_lowercase("hostgroup")
user = ansible_module.params_get_lowercase("user")
group = ansible_module.params_get_lowercase("group")
allow_sudocmd = ansible_module.params_get('allow_sudocmd')
allow_sudocmdgroup = ansible_module.params_get('allow_sudocmdgroup')
allow_sudocmdgroup = \
ansible_module.params_get_lowercase('allow_sudocmdgroup')
deny_sudocmd = ansible_module.params_get('deny_sudocmd')
deny_sudocmdgroup = ansible_module.params_get('deny_sudocmdgroup')
deny_sudocmdgroup = \
ansible_module.params_get_lowercase('deny_sudocmdgroup')
sudooption = ansible_module.params_get("sudooption")
order = ansible_module.params_get("order")
runasuser = ansible_module.params_get("runasuser")
runasgroup = ansible_module.params_get("runasgroup")
runasuser = ansible_module.params_get_lowercase("runasuser")
runasgroup = ansible_module.params_get_lowercase("runasgroup")
action = ansible_module.params_get("action")
# state
@@ -374,8 +376,26 @@ def main():
# Connect to IPA API
with ansible_module.ipa_connect():
default_domain = api_get_domain()
# Ensure host is not short hostname.
if host:
host = list(
{ensure_fqdn(value.lower(), default_domain) for value in host}
)
commands = []
host_add, host_del = [], []
user_add, user_del = [], []
group_add, group_del = [], []
hostgroup_add, hostgroup_del = [], []
allow_cmd_add, allow_cmd_del = [], []
allow_cmdgroup_add, allow_cmdgroup_del = [], []
deny_cmd_add, deny_cmd_del = [], []
deny_cmdgroup_add, deny_cmdgroup_del = [], []
sudooption_add, sudooption_del = [], []
runasuser_add, runasuser_del = [], []
runasgroup_add, runasgroup_del = [], []
for name in names:
# Make sure sudorule exists
@@ -480,95 +500,11 @@ def main():
runasgroup_add, runasgroup_del = gen_add_del_lists(
runasgroup,
(
res_find.get('ipasudorunas_group', [])
res_find.get('ipasudorunasgroup_group', [])
+ res_find.get('ipasudorunasextgroup', [])
)
)
# Add hosts and hostgroups
if len(host_add) > 0 or len(hostgroup_add) > 0:
commands.append([name, "sudorule_add_host",
{
"host": host_add,
"hostgroup": hostgroup_add,
}])
# Remove hosts and hostgroups
if len(host_del) > 0 or len(hostgroup_del) > 0:
commands.append([name, "sudorule_remove_host",
{
"host": host_del,
"hostgroup": hostgroup_del,
}])
# Add users and groups
if len(user_add) > 0 or len(group_add) > 0:
commands.append([name, "sudorule_add_user",
{
"user": user_add,
"group": group_add,
}])
# Remove users and groups
if len(user_del) > 0 or len(group_del) > 0:
commands.append([name, "sudorule_remove_user",
{
"user": user_del,
"group": group_del,
}])
# Add commands allowed
if len(allow_cmd_add) > 0 or len(allow_cmdgroup_add) > 0:
commands.append([name, "sudorule_add_allow_command",
{"sudocmd": allow_cmd_add,
"sudocmdgroup": allow_cmdgroup_add,
}])
if len(allow_cmd_del) > 0 or len(allow_cmdgroup_del) > 0:
commands.append([name, "sudorule_remove_allow_command",
{"sudocmd": allow_cmd_del,
"sudocmdgroup": allow_cmdgroup_del
}])
# Add commands denied
if len(deny_cmd_add) > 0 or len(deny_cmdgroup_add) > 0:
commands.append([name, "sudorule_add_deny_command",
{"sudocmd": deny_cmd_add,
"sudocmdgroup": deny_cmdgroup_add,
}])
if len(deny_cmd_del) > 0 or len(deny_cmdgroup_del) > 0:
commands.append([name, "sudorule_remove_deny_command",
{"sudocmd": deny_cmd_del,
"sudocmdgroup": deny_cmdgroup_del
}])
# Add RunAS Users
if len(runasuser_add) > 0:
commands.append([name, "sudorule_add_runasuser",
{"user": runasuser_add}])
# Remove RunAS Users
if len(runasuser_del) > 0:
commands.append([name, "sudorule_remove_runasuser",
{"user": runasuser_del}])
# Add RunAS Groups
if len(runasgroup_add) > 0:
commands.append([name, "sudorule_add_runasgroup",
{"group": runasgroup_add}])
# Remove RunAS Groups
if len(runasgroup_del) > 0:
commands.append([name, "sudorule_remove_runasgroup",
{"group": runasgroup_del}])
# Add sudo options
for sudoopt in sudooption_add:
commands.append([name, "sudorule_add_option",
{"ipasudoopt": sudoopt}])
# Remove sudo options
for sudoopt in sudooption_del:
commands.append([name, "sudorule_remove_option",
{"ipasudoopt": sudoopt}])
elif action == "member":
if res_find is None:
ansible_module.fail_json(msg="No sudorule '%s'" % name)
@@ -578,56 +514,47 @@ def main():
# deny_sudocmdgroup, sudooption, runasuser, runasgroup
# and res_find to only try to add the items that not in
# the sudorule already
if host is not None and \
"memberhost_host" in res_find:
host = gen_add_list(
host, res_find["memberhost_host"])
if hostgroup is not None and \
"memberhost_hostgroup" in res_find:
hostgroup = gen_add_list(
hostgroup, res_find["memberhost_hostgroup"])
if user is not None and \
"memberuser_user" in res_find:
user = gen_add_list(
user, res_find["memberuser_user"])
if group is not None and \
"memberuser_group" in res_find:
group = gen_add_list(
group, res_find["memberuser_group"])
if allow_sudocmd is not None and \
"memberallowcmd_sudocmd" in res_find:
allow_sudocmd = gen_add_list(
allow_sudocmd, res_find["memberallowcmd_sudocmd"])
if allow_sudocmdgroup is not None and \
"memberallowcmd_sudocmdgroup" in res_find:
allow_sudocmdgroup = gen_add_list(
if host is not None:
host_add = gen_add_list(
host, res_find.get("memberhost_host"))
if hostgroup is not None:
hostgroup_add = gen_add_list(
hostgroup, res_find.get("memberhost_hostgroup"))
if user is not None:
user_add = gen_add_list(
user, res_find.get("memberuser_user"))
if group is not None:
group_add = gen_add_list(
group, res_find.get("memberuser_group"))
if allow_sudocmd is not None:
allow_cmd_add = gen_add_list(
allow_sudocmd,
res_find.get("memberallowcmd_sudocmd")
)
if allow_sudocmdgroup is not None:
allow_cmdgroup_add = gen_add_list(
allow_sudocmdgroup,
res_find["memberallowcmd_sudocmdgroup"])
if deny_sudocmd is not None and \
"memberdenycmd_sudocmd" in res_find:
deny_sudocmd = gen_add_list(
deny_sudocmd, res_find["memberdenycmd_sudocmd"])
if deny_sudocmdgroup is not None and \
"memberdenycmd_sudocmdgroup" in res_find:
deny_sudocmdgroup = gen_add_list(
res_find.get("memberallowcmd_sudocmdgroup")
)
if deny_sudocmd is not None:
deny_cmd_add = gen_add_list(
deny_sudocmd,
res_find.get("memberdenycmd_sudocmd")
)
if deny_sudocmdgroup is not None:
deny_cmdgroup_add = gen_add_list(
deny_sudocmdgroup,
res_find["memberdenycmd_sudocmdgroup"])
if sudooption is not None and \
"ipasudoopt" in res_find:
sudooption = gen_add_list(
sudooption, res_find["ipasudoopt"])
res_find.get("memberdenycmd_sudocmdgroup")
)
if sudooption is not None:
sudooption_add = gen_add_list(
sudooption, res_find.get("ipasudoopt"))
# runasuser attribute can be used with both IPA and
# non-IPA (external) users, so we need to compare
# the provided list against both users and external
# users list.
if (
runasuser is not None
and (
"ipasudorunas_user" in res_find
or "ipasudorunasextuser" in res_find
)
):
runasuser = gen_add_list(
if runasuser is not None:
runasuser_add = gen_add_list(
runasuser,
(list(res_find.get('ipasudorunas_user', []))
+ list(res_find.get('ipasudorunasextuser', [])))
@@ -636,69 +563,13 @@ def main():
# non-IPA (external) groups, so we need to compare
# the provided list against both users and external
# groups list.
if (
runasgroup is not None
and (
"ipasudorunasgroup_group" in res_find
or "ipasudorunasextgroup" in res_find
)
):
runasgroup = gen_add_list(
if runasgroup is not None:
runasgroup_add = gen_add_list(
runasgroup,
(list(res_find.get("ipasudorunasgroup_group", []))
+ list(res_find.get("ipasudorunasextgroup", [])))
)
# Add hosts and hostgroups
if host is not None or hostgroup is not None:
commands.append([name, "sudorule_add_host",
{
"host": host,
"hostgroup": hostgroup,
}])
# Add users and groups
if user is not None or group is not None:
commands.append([name, "sudorule_add_user",
{
"user": user,
"group": group,
}])
# Add commands
if allow_sudocmd is not None \
or allow_sudocmdgroup is not None:
commands.append([name, "sudorule_add_allow_command",
{"sudocmd": allow_sudocmd,
"sudocmdgroup": allow_sudocmdgroup,
}])
# Add commands
if deny_sudocmd is not None \
or deny_sudocmdgroup is not None:
commands.append([name, "sudorule_add_deny_command",
{"sudocmd": deny_sudocmd,
"sudocmdgroup": deny_sudocmdgroup,
}])
# Add RunAS Users
if runasuser is not None and len(runasuser) > 0:
commands.append([name, "sudorule_add_runasuser",
{"user": runasuser}])
# Add RunAS Groups
if runasgroup is not None and len(runasgroup) > 0:
commands.append([name, "sudorule_add_runasgroup",
{"group": runasgroup}])
# Add options
if sudooption is not None:
existing_opts = res_find.get('ipasudoopt', [])
for sudoopt in sudooption:
if sudoopt not in existing_opts:
commands.append([name, "sudorule_add_option",
{"ipasudoopt": sudoopt}])
elif state == "absent":
if action == "sudorule":
if res_find is not None:
@@ -714,153 +585,70 @@ def main():
# and res_find to only try to remove the items that are
# in sudorule
if host is not None:
if "memberhost_host" in res_find:
host = gen_intersection_list(
host, res_find["memberhost_host"])
else:
host = None
host_del = gen_intersection_list(
host, res_find.get("memberhost_host"))
if hostgroup is not None:
if "memberhost_hostgroup" in res_find:
hostgroup = gen_intersection_list(
hostgroup, res_find["memberhost_hostgroup"])
else:
hostgroup = None
hostgroup_del = gen_intersection_list(
hostgroup, res_find.get("memberhost_hostgroup"))
if user is not None:
if "memberuser_user" in res_find:
user = gen_intersection_list(
user, res_find["memberuser_user"])
else:
user = None
user_del = gen_intersection_list(
user, res_find.get("memberuser_user"))
if group is not None:
if "memberuser_group" in res_find:
group = gen_intersection_list(
group, res_find["memberuser_group"])
else:
group = None
group_del = gen_intersection_list(
group, res_find.get("memberuser_group"))
if allow_sudocmd is not None:
if "memberallowcmd_sudocmd" in res_find:
allow_sudocmd = gen_intersection_list(
allow_sudocmd,
res_find["memberallowcmd_sudocmd"])
else:
allow_sudocmd = None
allow_cmd_del = gen_intersection_list(
allow_sudocmd,
res_find.get("memberallowcmd_sudocmd")
)
if allow_sudocmdgroup is not None:
if "memberallowcmd_sudocmdgroup" in res_find:
allow_sudocmdgroup = gen_intersection_list(
allow_sudocmdgroup,
res_find["memberallowcmd_sudocmdgroup"])
else:
allow_sudocmdgroup = None
allow_cmdgroup_del = gen_intersection_list(
allow_sudocmdgroup,
res_find.get("memberallowcmd_sudocmdgroup")
)
if deny_sudocmd is not None:
if "memberdenycmd_sudocmd" in res_find:
deny_sudocmd = gen_intersection_list(
deny_sudocmd,
res_find["memberdenycmd_sudocmd"])
else:
deny_sudocmd = None
deny_cmd_del = gen_intersection_list(
deny_sudocmd,
res_find.get("memberdenycmd_sudocmd")
)
if deny_sudocmdgroup is not None:
if "memberdenycmd_sudocmdgroup" in res_find:
deny_sudocmdgroup = gen_intersection_list(
deny_sudocmdgroup,
res_find["memberdenycmd_sudocmdgroup"])
else:
deny_sudocmdgroup = None
deny_cmdgroup_del = gen_intersection_list(
deny_sudocmdgroup,
res_find.get("memberdenycmd_sudocmdgroup")
)
if sudooption is not None:
if "ipasudoopt" in res_find:
sudooption = gen_intersection_list(
sudooption, res_find["ipasudoopt"])
else:
sudooption = None
sudooption_del = gen_intersection_list(
sudooption, res_find.get("ipasudoopt"))
# runasuser attribute can be used with both IPA and
# non-IPA (external) users, so we need to compare
# the provided list against both users and external
# users list.
if runasuser is not None:
if (
"ipasudorunas_user" in res_find
or "ipasudorunasextuser" in res_find
):
runasuser = gen_intersection_list(
runasuser,
(
list(res_find.get('ipasudorunas_user', []))
+ list(res_find.get(
'ipasudorunasextuser', []))
)
runasuser_del = gen_intersection_list(
runasuser,
(
list(res_find.get('ipasudorunas_user', []))
+ list(res_find.get('ipasudorunasextuser', []))
)
else:
runasuser = None
)
# runasgroup attribute can be used with both IPA and
# non-IPA (external) groups, so we need to compare
# the provided list against both groups and external
# groups list.
if runasgroup is not None:
if (
"ipasudorunasgroup_group" in res_find
or "ipasudorunasextgroup" in res_find
):
runasgroup = gen_intersection_list(
runasgroup,
(
list(res_find.get(
"ipasudorunasgroup_group", []))
+ list(res_find.get(
"ipasudorunasextgroup", []))
)
runasgroup_del = gen_intersection_list(
runasgroup,
(
list(res_find.get(
"ipasudorunasgroup_group", []))
+ list(res_find.get(
"ipasudorunasextgroup", []))
)
else:
runasgroup = None
# Remove hosts and hostgroups
if host is not None or hostgroup is not None:
commands.append([name, "sudorule_remove_host",
{
"host": host,
"hostgroup": hostgroup,
}])
# Remove users and groups
if user is not None or group is not None:
commands.append([name, "sudorule_remove_user",
{
"user": user,
"group": group,
}])
# Remove allow commands
if allow_sudocmd is not None \
or allow_sudocmdgroup is not None:
commands.append([name, "sudorule_remove_allow_command",
{"sudocmd": allow_sudocmd,
"sudocmdgroup": allow_sudocmdgroup
}])
# Remove deny commands
if deny_sudocmd is not None \
or deny_sudocmdgroup is not None:
commands.append([name, "sudorule_remove_deny_command",
{"sudocmd": deny_sudocmd,
"sudocmdgroup": deny_sudocmdgroup
}])
# Remove RunAS Users
if runasuser is not None:
commands.append([name, "sudorule_remove_runasuser",
{"user": runasuser}])
# Remove RunAS Groups
if runasgroup is not None:
commands.append([name, "sudorule_remove_runasgroup",
{"group": runasgroup}])
# Remove options
if sudooption is not None:
existing_opts = res_find.get('ipasudoopt', [])
for sudoopt in sudooption:
if sudoopt in existing_opts:
commands.append([name,
"sudorule_remove_option",
{"ipasudoopt": sudoopt}])
)
elif state == "enabled":
if res_find is None:
@@ -868,8 +656,12 @@ def main():
# sudorule_enable is not failing on an enabled sudorule
# Therefore it is needed to have a look at the ipaenabledflag
# in res_find.
if "ipaenabledflag" not in res_find or \
res_find["ipaenabledflag"][0] != "TRUE":
# FreeIPA 4.9.10+ and 4.10 use proper mapping for
# boolean values, so we need to convert it to str
# for comparison.
# See: https://github.com/freeipa/freeipa/pull/6294
enabled_flag = str(res_find.get("ipaenabledflag", [False])[0])
if enabled_flag.upper() != "TRUE":
commands.append([name, "sudorule_enable", {}])
elif state == "disabled":
@@ -878,13 +670,110 @@ def main():
# sudorule_disable is not failing on an disabled sudorule
# Therefore it is needed to have a look at the ipaenabledflag
# in res_find.
if "ipaenabledflag" not in res_find or \
res_find["ipaenabledflag"][0] != "FALSE":
# FreeIPA 4.9.10+ and 4.10 use proper mapping for
# boolean values, so we need to convert it to str
# for comparison.
# See: https://github.com/freeipa/freeipa/pull/6294
enabled_flag = str(res_find.get("ipaenabledflag", [False])[0])
if enabled_flag.upper() != "FALSE":
commands.append([name, "sudorule_disable", {}])
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Manage members.
# Manage hosts and hostgroups
if host_add or hostgroup_add:
commands.append([name, "sudorule_add_host",
{
"host": host_add,
"hostgroup": hostgroup_add,
}])
if host_del or hostgroup_del:
commands.append([name, "sudorule_remove_host",
{
"host": host_del,
"hostgroup": hostgroup_del,
}])
# Manage users and groups
if user_add or group_add:
commands.append([
name, "sudorule_add_user",
{"user": user_add, "group": group_add}
])
if user_del or group_del:
commands.append([
name, "sudorule_remove_user",
{"user": user_del, "group": group_del}
])
# Manage commands allowed
if allow_cmd_add or allow_cmdgroup_add:
commands.append([
name, "sudorule_add_allow_command",
{
"sudocmd": allow_cmd_add,
"sudocmdgroup": allow_cmdgroup_add,
}
])
if allow_cmd_del or allow_cmdgroup_del:
commands.append([
name, "sudorule_remove_allow_command",
{
"sudocmd": allow_cmd_del,
"sudocmdgroup": allow_cmdgroup_del
}
])
# Manage commands denied
if deny_cmd_add or deny_cmdgroup_add:
commands.append([
name, "sudorule_add_deny_command",
{
"sudocmd": deny_cmd_add,
"sudocmdgroup": deny_cmdgroup_add,
}
])
if deny_cmd_del or deny_cmdgroup_del:
commands.append([
name, "sudorule_remove_deny_command",
{
"sudocmd": deny_cmd_del,
"sudocmdgroup": deny_cmdgroup_del
}
])
# Manage RunAS users
if runasuser_add:
commands.append([
name, "sudorule_add_runasuser", {"user": runasuser_add}
])
if runasuser_del:
commands.append([
name, "sudorule_remove_runasuser", {"user": runasuser_del}
])
# Manage RunAS Groups
if runasgroup_add:
commands.append([
name, "sudorule_add_runasgroup", {"group": runasgroup_add}
])
if runasgroup_del:
commands.append([
name, "sudorule_remove_runasgroup",
{"group": runasgroup_del}
])
# Manage sudo options
if sudooption_add:
for option in sudooption_add:
commands.append([
name, "sudorule_add_option", {"ipasudoopt": option}
])
if sudooption_del:
for option in sudooption_del:
commands.append([
name, "sudorule_remove_option", {"ipasudoopt": option}
])
# Execute commands
changed = ansible_module.execute_ipa_commands(

View File

@@ -44,7 +44,8 @@ options:
description:
- Trust type (ad for Active Directory, default)
default: ad
required: true
required: false
choices: ["ad"]
admin:
description:
- Active Directory domain administrator
@@ -103,7 +104,7 @@ EXAMPLES = """
realm: ad.example.test
trust_type: ad
admin: Administrator
password: Welcome2020!
password: SomeW1Npassword
state: present
# delete ad-trust
@@ -157,7 +158,7 @@ def add_trust(module, realm, args):
def gen_args(trust_type, admin, password, server, trust_secret, base_id,
range_size, _range_type, two_way, external):
range_size, range_type, two_way, external):
_args = {}
if trust_type is not None:
_args["trust_type"] = trust_type
@@ -173,6 +174,8 @@ def gen_args(trust_type, admin, password, server, trust_secret, base_id,
_args["base_id"] = base_id
if range_size is not None:
_args["range_size"] = range_size
if range_type is not None:
_args["range_type"] = range_type
if two_way is not None:
_args["bidirectional"] = two_way
if external is not None:
@@ -190,7 +193,8 @@ def main():
state=dict(type="str", default="present",
choices=["present", "absent"]),
# present
trust_type=dict(type="str", default="ad", required=False),
trust_type=dict(type="str", default="ad", required=False,
choices=["ad"]),
admin=dict(type="str", default=None, required=False),
password=dict(type="str", default=None,
required=False, no_log=True),

View File

@@ -474,41 +474,31 @@ user:
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, date_format, \
encode_certificate, load_cert_from_str, DN_x500_text, to_text
encode_certificate, load_cert_from_str, DN_x500_text, to_text, \
ipalib_errors
from ansible.module_utils import six
if six.PY3:
unicode = str
def find_user(module, name, preserved=False):
def find_user(module, name):
_args = {
"all": True,
"uid": name,
}
if preserved:
_args["preserved"] = preserved
_result = module.ipa_command("user_find", name, _args)
try:
_result = module.ipa_command("user_show", name, _args).get("result")
except ipalib_errors.NotFound:
return None
if len(_result["result"]) > 1:
module.fail_json(
msg="There is more than one user '%s'" % (name))
elif len(_result["result"]) == 1:
# Transform each principal to a string
_result = _result["result"][0]
if "krbprincipalname" in _result \
and _result["krbprincipalname"] is not None:
_list = []
for x in _result["krbprincipalname"]:
_list.append(str(x))
_result["krbprincipalname"] = _list
certs = _result.get("usercertificate")
if certs is not None:
_result["usercertificate"] = [encode_certificate(x)
for x in certs]
return _result
return None
# Transform each principal to a string
_result["krbprincipalname"] = [
to_text(x) for x in (_result.get("krbprincipalname") or [])
]
_result["usercertificate"] = [
encode_certificate(x) for x in (_result.get("usercertificate") or [])
]
return _result
def gen_args(first, last, fullname, displayname, initials, homedir, shell,
@@ -902,8 +892,10 @@ def main():
title = ansible_module.params_get("title")
manager = ansible_module.params_get("manager")
carlicense = ansible_module.params_get("carlicense")
sshpubkey = ansible_module.params_get("sshpubkey")
userauthtype = ansible_module.params_get("userauthtype")
sshpubkey = ansible_module.params_get("sshpubkey",
allow_empty_string=True)
userauthtype = ansible_module.params_get("userauthtype",
allow_empty_string=True)
userclass = ansible_module.params_get("userclass")
radius = ansible_module.params_get("radius")
radiususer = ansible_module.params_get("radiususer")
@@ -1085,12 +1077,6 @@ def main():
# Make sure user exists
res_find = find_user(ansible_module, name)
# Also search for preserved user if the user could not be found
if res_find is None:
res_find_preserved = find_user(ansible_module, name,
preserved=True)
else:
res_find_preserved = None
# Create command
if state == "present":
@@ -1104,10 +1090,6 @@ def main():
departmentnumber, employeenumber, employeetype,
preferredlanguage, noprivate, nomembers)
# Also check preserved users
if res_find is None and res_find_preserved is not None:
res_find = res_find_preserved
if action == "user":
# Found the user
if res_find is not None:
@@ -1121,13 +1103,6 @@ def main():
if "noprivate" in args:
del args["noprivate"]
# Ignore userauthtype if it is empty (for resetting)
# and not set in for the user
if "ipauserauthtype" not in res_find and \
"ipauserauthtype" in args and \
args["ipauserauthtype"] == ['']:
del args["ipauserauthtype"]
# For all settings is args, check if there are
# different settings in the find result.
# If yes: modify
@@ -1310,16 +1285,16 @@ def main():
gen_certmapdata_args(_data)])
elif state == "absent":
# Also check preserved users
if res_find is None and res_find_preserved is not None:
res_find = res_find_preserved
if action == "user":
if res_find is not None:
args = {}
if preserve is not None:
args["preserve"] = preserve
commands.append([name, "user_del", args])
if (
not res_find.get("preserved", False)
or not args.get("preserve", False)
):
commands.append([name, "user_del", args])
elif action == "member":
if res_find is None:
ansible_module.fail_json(
@@ -1370,17 +1345,18 @@ def main():
commands.append([name, "user_remove_certmapdata",
gen_certmapdata_args(_data)])
elif state == "undeleted":
if res_find_preserved is not None:
commands.append([name, "user_undel", {}])
if res_find is not None:
if res_find.get("preserved", False):
commands.append([name, "user_undel", {}])
else:
raise ValueError("No preserved user '%s'" % name)
raise ValueError("No user '%s'" % name)
elif state == "enabled":
if res_find is not None:
if res_find["nsaccountlock"]:
commands.append([name, "user_enable", {}])
else:
raise ValueError("No disabled user '%s'" % name)
raise ValueError("No user '%s'" % name)
elif state == "disabled":
if res_find is not None:
@@ -1392,6 +1368,8 @@ def main():
elif state == "unlocked":
if res_find is not None:
commands.append([name, "user_unlock", {}])
else:
raise ValueError("No user '%s'" % name)
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)

View File

@@ -1,5 +1,12 @@
-r requirements-tests.txt
ipdb
ipdb==0.13.4
pre-commit
flake8==4.0.1
flake8-bugbear
pylint==2.10.2
pylint==2.13.7
pydocstyle==6.0.0
yamllint==1.26.3
ansible-lint==5.3.2
dnspython==2.2.0
netaddr==0.8.0
gssapi==1.7.2

View File

@@ -61,7 +61,7 @@ from ipaplatform.paths import paths
def main():
module = AnsibleModule(
argument_spec=dict(),
argument_spec={},
supports_check_mode=True,
)

View File

@@ -33,7 +33,6 @@ Requirements
**Controller**
* Ansible version: 2.8+
* /usr/bin/kinit is required on the controller if a one time password (OTP) is used
* python3-gssapi is required on the controller if a one time password (OTP) is used with keytab
**Node**
* Supported FreeIPA version (see above)

View File

@@ -21,10 +21,6 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
try:
import gssapi
except ImportError:
gssapi = None
import os
import shutil
import subprocess
@@ -45,12 +41,14 @@ def run_cmd(args, stdin=None):
if stdin:
p_in = subprocess.PIPE
p = subprocess.Popen(args, stdin=p_in, stdout=p_out, stderr=p_err,
close_fds=True)
__temp, stderr = p.communicate(stdin)
# pylint: disable=invalid-name
with subprocess.Popen(
args, stdin=p_in, stdout=p_out, stderr=p_err, close_fds=True
) as p:
__temp, stderr = p.communicate(stdin)
if p.returncode != 0:
raise RuntimeError(stderr)
if p.returncode != 0:
raise RuntimeError(stderr)
def kinit_password(principal, password, ccache_name, config):
@@ -80,22 +78,17 @@ def kinit_keytab(principal, keytab, ccache_name, config):
It uses the specified config file to kinit and stores the TGT
in ccache_name.
"""
if gssapi is None:
raise ImportError("gssapi is not available")
args = ["/usr/bin/kinit", "-kt", keytab, "-c", ccache_name, principal]
old_config = os.environ.get('KRB5_CONFIG')
os.environ['KRB5_CONFIG'] = config
os.environ["KRB5_CONFIG"] = config
try:
name = gssapi.Name(principal, gssapi.NameType.kerberos_principal)
store = {'ccache': ccache_name,
'client_keytab': keytab}
cred = gssapi.Credentials(name=name, store=store, usage='initiate')
return cred
return run_cmd(args)
finally:
if old_config is not None:
os.environ['KRB5_CONFIG'] = old_config
os.environ["KRB5_CONFIG"] = old_config
else:
os.environ.pop('KRB5_CONFIG', None)
os.environ.pop("KRB5_CONFIG", None)
KRB5CONF_TEMPLATE = """
@@ -128,8 +121,9 @@ KRB5CONF_TEMPLATE = """
"""
class ActionModule(ActionBase):
class ActionModule(ActionBase): # pylint: disable=too-few-public-methods
# pylint: disable=too-many-return-statements
def run(self, tmp=None, task_vars=None):
"""
Handle credential cache transfer.
@@ -149,8 +143,9 @@ class ActionModule(ActionBase):
Then the IPA commands can use this credential cache file.
"""
if task_vars is None:
task_vars = dict()
task_vars = {}
# pylint: disable=super-with-arguments
result = super(ActionModule, self).run(tmp, task_vars)
principal = self._task.args.get('principal', None)
keytab = self._task.args.get('keytab', None)
@@ -168,7 +163,7 @@ class ActionModule(ActionBase):
return result
data = self._execute_module(module_name='ipaclient_get_facts',
module_args=dict(), task_vars=task_vars)
module_args={}, task_vars=task_vars)
try:
domain = data['ansible_facts']['ipa']['domain']
@@ -195,7 +190,7 @@ class ActionModule(ActionBase):
ipa_realm=realm,
ipa_lifetime=lifetime))
with open(krb5conf_name, 'w') as f:
with open(krb5conf_name, 'w') as f: # pylint: disable=invalid-name
f.write(content)
if password:

View File

@@ -75,7 +75,6 @@ subject_base:
'''
import os
import inspect
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
@@ -83,7 +82,7 @@ from ansible.module_utils.ansible_ipa_client import (
paths, x509, NUM_VERSION, serialization, certdb, api,
delete_persistent_client_session_data, write_tmp_file,
ipa_generate_password, CalledProcessError, errors, disable_ra, DN,
CLIENT_INSTALL_ERROR, logger
CLIENT_INSTALL_ERROR, logger, getargspec
)
@@ -133,7 +132,9 @@ def main():
# Add CA certs to a temporary NSS database
try:
argspec = inspect.getargspec(tmp_db.create_db)
# pylint: disable=deprecated-method
argspec = getargspec(tmp_db.create_db)
# pylint: enable=deprecated-method
if "password_filename" not in argspec.args:
tmp_db.create_db()
else:
@@ -181,7 +182,7 @@ def main():
module.warn(
"Some capabilities including the ipa command capability "
"may not be available")
except errors.PublicError as e2:
except errors.PublicError as e2: # pylint: disable=invalid-name
module.fail_json(
msg="Cannot connect to the IPA server RPC interface: "
"%s" % e2)

View File

@@ -56,10 +56,12 @@ def is_ntpd_configured():
ntpd_conf_section = re.compile(r'^\s*\[ntpd\]\s*$')
try:
# pylint: disable=invalid-name
with open(SERVER_SYSRESTORE_STATE) as f:
for line in f.readlines():
if ntpd_conf_section.match(line):
return True
# pylint: enable=invalid-name
return False
except IOError:
return False
@@ -71,7 +73,7 @@ def is_dns_configured():
bind_conf_section = re.compile(r'^\s*dyndb\s+"ipa"\s+"[^"]+"\s+{$')
try:
with open(NAMED_CONF) as f:
with open(NAMED_CONF) as f: # pylint: disable=invalid-name
for line in f.readlines():
if bind_conf_section.match(line):
return True
@@ -103,7 +105,7 @@ def is_client_configured():
# and /var/lib/ipa-client/sysrestore/sysrestore.state exists
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
return (os.path.isfile(paths.IPA_DEFAULT_CONF) and fstore.has_files())
return os.path.isfile(paths.IPA_DEFAULT_CONF) and fstore.has_files()
def is_server_configured():
@@ -129,7 +131,9 @@ def get_ipa_conf():
def get_ipa_version():
try:
# pylint: disable=import-outside-toplevel
from ipapython import version
# pylint: enable=import-outside-toplevel
except ImportError:
return None
else:
@@ -156,7 +160,7 @@ def get_ipa_version():
def main():
module = AnsibleModule(
argument_spec=dict(),
argument_spec={},
supports_check_mode=True
)

View File

@@ -146,7 +146,7 @@ def get_host_diff(ipa_host, module_host):
:return: a dict representing the host attributes to apply
"""
non_updateable_keys = ['ip_address']
data = dict()
data = {}
for key in non_updateable_keys:
if key in module_host:
del module_host[key]
@@ -173,7 +173,7 @@ def get_module_host(module):
:param module: the ansible module
:returns: a dict representing the host attributes
"""
data = dict()
data = {}
certificates = module.params.get('certificates')
if certificates:
data['usercertificate'] = certificates
@@ -189,7 +189,7 @@ def get_module_host(module):
return data
def ensure_host_present(module, api, ipahost):
def ensure_host_present(module, _api, ipahost):
"""
Ensure host exists in IPA and has the same attributes.
@@ -216,13 +216,13 @@ def ensure_host_present(module, api, ipahost):
# already has Keytab: true, then we need first to run
# ipa host-disable in order to remove OTP and keytab
if module.params.get('random') and ipahost['has_keytab'] is True:
api.Command.host_disable(fqdn)
_api.Command.host_disable(fqdn)
result = api.Command.host_mod(fqdn, **diffs)
result = _api.Command.host_mod(fqdn, **diffs)
# Save random password as it is not displayed by host-show
if module.params.get('random'):
randompassword = result['result']['randompassword']
result = api.Command.host_show(fqdn)
result = _api.Command.host_show(fqdn)
if module.params.get('random'):
result['result']['randompassword'] = randompassword
module.exit_json(changed=True, host=result['result'])
@@ -236,17 +236,17 @@ def ensure_host_present(module, api, ipahost):
module_host = get_module_host(module)
# force creation of host even if there is no DNS record
module_host["force"] = True
result = api.Command.host_add(fqdn, **module_host)
result = _api.Command.host_add(fqdn, **module_host)
# Save random password as it is not displayed by host-show
if module.params.get('random'):
randompassword = result['result']['randompassword']
result = api.Command.host_show(fqdn)
result = _api.Command.host_show(fqdn)
if module.params.get('random'):
result['result']['randompassword'] = randompassword
module.exit_json(changed=True, host=result['result'])
def ensure_host_absent(module, api, host):
def ensure_host_absent(module, _api, host):
"""
Ensure host does not exist in IPA.
@@ -265,7 +265,7 @@ def ensure_host_absent(module, api, host):
fqdn = unicode(module.params.get('fqdn'))
try:
api.Command.host_del(fqdn)
_api.Command.host_del(fqdn)
except Exception as e:
module.fail_json(msg="Failed to remove host: %s" % e)

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