Compare commits

...

63 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
85 changed files with 2648 additions and 459 deletions

View File

@@ -175,8 +175,8 @@ Variable | Description | Required
`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
`dom_name` \| `ipanttrusteddomainname` | Name 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

View File

@@ -12,6 +12,7 @@ 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
@@ -425,6 +426,8 @@ 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
=========================

View File

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

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

View File

@@ -2,8 +2,8 @@
driver:
name: docker
platforms:
- name: centos-8
image: quay.io/ansible-freeipa/upstream-tests:centos-8
- name: fedora-rawhide
image: quay.io/ansible-freeipa/upstream-tests:fedora-rawhide
pre_build_image: true
hostname: ipaserver.test.local
dns_servers:

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

@@ -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,6 +87,7 @@ 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
@@ -139,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
@@ -221,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))
@@ -252,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))
@@ -289,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 = [
@@ -701,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.
@@ -799,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
@@ -1182,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

@@ -441,7 +441,11 @@ 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(

View File

@@ -173,7 +173,10 @@ def gen_args(module, state, action, 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
@@ -199,7 +202,8 @@ def main():
choices=["member", "dnsconfig"]),
state=dict(type="str", default="present",
choices=["present", "absent"]),
)
),
supports_check_mode=True,
)
ansible_module._ansible_debug = True

View File

@@ -344,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

@@ -472,18 +472,26 @@ 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:

View File

@@ -74,7 +74,9 @@ options:
required: false
aliases: ["ipanttrusteddomainsid"]
dom_name:
description: Domain name of the trusted domain.
description: |
Domain name of the trusted domain. Can only be used when
`ipaapi_context: server`.
type: string
required: false
aliases: ["ipanttrusteddomainname"]
@@ -134,7 +136,7 @@ RETURN = """
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa
IPAAnsibleModule, compare_args_ipa, get_trusted_domain_sid_from_name
from ansible.module_utils import six
if six.PY3:
@@ -154,7 +156,7 @@ def find_idrange(module, name):
def gen_args(
base_id, range_size, rid_base, secondary_rid_base, idrange_type, dom_sid,
auto_private_groups
dom_name, auto_private_groups
):
_args = {}
# Integer parameters are stored as strings.
@@ -169,6 +171,8 @@ def gen_args(
_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:
@@ -230,6 +234,7 @@ def main():
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")
@@ -248,7 +253,10 @@ def main():
if state == "absent":
if len(names) < 1:
ansible_module.fail_json(msg="No name given.")
invalid = ["base_id", "range_size", "idrange_type", "dom_sid"]
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)
@@ -278,7 +286,7 @@ def main():
# Generate args
args = gen_args(
base_id, range_size, rid_base, secondary_rid_base,
idrange_type, dom_sid, auto_private_groups
idrange_type, dom_sid, dom_name, auto_private_groups
)
# Found the idrange

View File

@@ -656,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":
@@ -666,8 +670,12 @@ 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:

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.12.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

@@ -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
)
@@ -134,7 +133,7 @@ def main():
# Add CA certs to a temporary NSS database
try:
# pylint: disable=deprecated-method
argspec = inspect.getargspec(tmp_db.create_db)
argspec = getargspec(tmp_db.create_db)
# pylint: enable=deprecated-method
if "password_filename" not in argspec.args:
tmp_db.create_db()

View File

@@ -57,11 +57,10 @@ EXAMPLES = '''
RETURN = '''
'''
import inspect
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
setup_logging, options, sysrestore, paths, configure_nisdomain
setup_logging, options, sysrestore, paths, configure_nisdomain,
getargspec
)
@@ -83,7 +82,7 @@ def main():
statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE)
# pylint: disable=deprecated-method
argspec = inspect.getargspec(configure_nisdomain)
argspec = getargspec(configure_nisdomain)
# pylint: enable=deprecated-method
if "statestore" not in argspec.args:
# NUM_VERSION < 40500:

View File

@@ -141,7 +141,6 @@ RETURN = '''
import os
import time
import inspect
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
@@ -151,7 +150,7 @@ from ansible.module_utils.ansible_ipa_client import (
get_certs_from_ldap, DN, certstore, x509, logger, certdb,
CalledProcessError, tasks, client_dns, configure_certmonger, services,
update_ssh_keys, save_state, configure_ldap_conf, configure_nslcd_conf,
configure_openldap_conf, hardcode_ldap_server
configure_openldap_conf, hardcode_ldap_server, getargspec
)
@@ -323,7 +322,7 @@ def main():
pass
# pylint: disable=deprecated-method
argspec_save_state = inspect.getargspec(save_state)
argspec_save_state = getargspec(save_state)
# Name Server Caching Daemon. Disable for SSSD, use otherwise
# (if installed)
@@ -387,7 +386,7 @@ def main():
if not options.no_ac:
# Modify nsswitch/pam stack
# pylint: disable=deprecated-method
argspec = inspect.getargspec(tasks.modify_nsswitch_pam_stack)
argspec = getargspec(tasks.modify_nsswitch_pam_stack)
if "sudo" in argspec.args:
tasks.modify_nsswitch_pam_stack(
sssd=options.sssd,

View File

@@ -66,13 +66,11 @@ EXAMPLES = '''
RETURN = '''
'''
import inspect
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
setup_logging,
options, sysrestore, paths, sync_time, logger, ipadiscovery,
timeconf
timeconf, getargspec
)
@@ -114,7 +112,7 @@ def main():
if options.conf_ntp:
# Attempt to configure and sync time with NTP server (chrony).
# pylint: disable=deprecated-method
argspec = inspect.getargspec(sync_time)
argspec = getargspec(sync_time)
# pylint: enable=deprecated-method
if "options" not in argspec.args:
synced_ntp = sync_time(options.ntp_servers, options.ntp_pool,

View File

@@ -197,7 +197,6 @@ nosssd_files:
import os
import socket
import inspect
try:
from ansible.module_utils.six.moves.configparser import RawConfigParser
@@ -212,7 +211,7 @@ from ansible.module_utils.ansible_ipa_client import (
CLIENT_INSTALL_ERROR, tasks, check_ldap_conf, timeconf, constants,
validate_hostname, nssldap_exists, gssapi, remove_file,
check_ip_addresses, ipadiscovery, print_port_conf_info,
IPA_PYTHON_VERSION
IPA_PYTHON_VERSION, getargspec
)
@@ -344,7 +343,7 @@ def main():
if options.realm_name:
# pylint: disable=deprecated-method
argspec = inspect.getargspec(validate_domain_name)
argspec = getargspec(validate_domain_name)
if "entity" in argspec.args:
# NUM_VERSION >= 40690:
validate_domain_name(options.realm_name, entity="realm")
@@ -881,7 +880,6 @@ def main():
is_ipaddr = False
if is_ipaddr:
logger.info()
logger.warning(
"It seems that you are using an IP address "
"instead of FQDN as an argument to --server. The "

View File

@@ -46,7 +46,7 @@ __all__ = ["gssapi", "version", "ipadiscovery", "api", "errors", "x509",
"configure_nslcd_conf", "configure_ssh_config",
"configure_sshd_config", "configure_automount",
"configure_firefox", "sync_time", "check_ldap_conf",
"sssd_enable_ifp"]
"sssd_enable_ifp", "getargspec"]
import sys
@@ -110,10 +110,31 @@ else:
# IPA version >= 4.4
# import sys
import inspect
import gssapi
import logging
# 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)
from ipapython import version
try:
from ipaclient.install import ipadiscovery
@@ -200,7 +221,7 @@ else:
sys.path.remove(temp_dir)
# pylint: disable=deprecated-method
argspec = inspect.getargspec(
argspec = getargspec(
ipa_client_install.configure_krb5_conf)
if argspec.keywords is None:
def configure_krb5_conf(
@@ -240,7 +261,7 @@ else:
create_ipa_nssdb = certdb.create_ipa_nssdb
argspec = \
inspect.getargspec(ipa_client_install.configure_nisdomain)
getargspec(ipa_client_install.configure_nisdomain)
if len(argspec.args) == 3:
configure_nisdomain = ipa_client_install.configure_nisdomain
else:

View File

@@ -96,13 +96,13 @@ RETURN = '''
'''
import os
import inspect
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_replica import (
AnsibleModuleLog, setup_logging, installer, DN, paths,
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
gen_ReplicaConfig, gen_remote_api, redirect_stdout, custodiainstance
gen_ReplicaConfig, gen_remote_api, redirect_stdout, custodiainstance,
getargspec
)
@@ -200,7 +200,7 @@ def main():
ansible_log.debug("-- CUSTODIA IMPORT DM PASSWORD --")
# pylint: disable=deprecated-method
argspec = inspect.getargspec(custodia.import_dm_password)
argspec = getargspec(custodia.import_dm_password)
# pylint: enable=deprecated-method
if "master_host_name" in argspec.args:
custodia.import_dm_password(config.master_host_name)

View File

@@ -182,6 +182,9 @@ options:
skip_conncheck:
description: Skip connection check to remote master
required: yes
sid_generation_always:
description: Enable SID generation always
required: yes
author:
- Thomas Woerner
'''
@@ -275,6 +278,8 @@ def main():
# additional
server=dict(required=True),
skip_conncheck=dict(required=False, type='bool'),
sid_generation_always=dict(required=False, type='bool',
default=False),
),
supports_check_mode=True,
)
@@ -350,6 +355,13 @@ def main():
# '_hostname_overridden')
options.server = ansible_module.params.get('server')
options.skip_conncheck = ansible_module.params.get('skip_conncheck')
sid_generation_always = ansible_module.params.get('sid_generation_always')
# random serial numbers are master_only, therefore setting to False
options.random_serial_numbers = False
# options._random_serial_numbers is generated by ca.install_check and
# later used by ca.install in the _setup_ca module.
options._random_serial_numbers = False
# init #
@@ -755,7 +767,7 @@ def main():
ansible_log.debug("-- CHECK ADTRUST --")
if options.setup_adtrust:
if options.setup_adtrust or sid_generation_always:
adtrust.install_check(False, options, remote_api)
except errors.ACIError:
@@ -838,6 +850,7 @@ def main():
_http_ca_cert=http_ca_cert,
_pkinit_pkcs12_info=pkinit_pkcs12_info,
_pkinit_ca_cert=pkinit_ca_cert,
_random_serial_numbers=options._random_serial_numbers,
no_dnssec_validation=options.no_dnssec_validation,
config_setup_ca=config.setup_ca,
config_master_host_name=config.master_host_name,

View File

@@ -71,6 +71,9 @@ options:
setup_ca:
description: Configure a dogtag CA
required: no
setup_adtrust:
description: Configure AD trust capability
required: yes
config_master_host_name:
description: The config master_host_name setting
required: no
@@ -112,6 +115,7 @@ def main():
ccache=dict(required=True),
_top_dir=dict(required=True),
setup_ca=dict(required=True, type='bool'),
setup_adtrust=dict(required=True, type='bool'),
config_master_host_name=dict(required=True),
),
supports_check_mode=True,
@@ -140,6 +144,7 @@ def main():
os.environ['KRB5CCNAME'] = ccache
options._top_dir = ansible_module.params.get('_top_dir')
options.setup_ca = ansible_module.params.get('setup_ca')
options.setup_adtrust = ansible_module.params.get('setup_adtrust')
config_master_host_name = ansible_module.params.get(
'config_master_host_name')
adtrust.netbios_name = ansible_module.params.get('adtrust_netbios_name')

View File

@@ -85,6 +85,9 @@ options:
_subject_base:
description: The installer _subject_base setting
required: no
_random_serial_numbers:
description: The installer _random_serial_numbers setting
required: yes
dirman_password:
description: Directory Manager (master) password
required: no
@@ -144,6 +147,7 @@ def main():
_top_dir=dict(required=True),
_ca_subject=dict(required=True),
_subject_base=dict(required=True),
_random_serial_numbers=dict(required=True, type='bool'),
dirman_password=dict(required=True, no_log=True),
config_setup_ca=dict(required=True, type='bool'),
config_master_host_name=dict(required=True),
@@ -190,6 +194,8 @@ def main():
options._subject_base = ansible_module.params.get('_subject_base')
if options._subject_base is not None:
options._subject_base = DN(options._subject_base)
options._random_serial_numbers = ansible_module.params.get(
'_random_serial_numbers')
dirman_password = ansible_module.params.get('dirman_password')
config_setup_ca = ansible_module.params.get('config_setup_ca')
config_master_host_name = ansible_module.params.get(

View File

@@ -149,7 +149,6 @@ RETURN = '''
'''
import os
import inspect
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_replica import (
@@ -157,7 +156,8 @@ from ansible.module_utils.ansible_ipa_replica import (
ansible_module_get_parsed_ip_addresses,
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
gen_ReplicaConfig, gen_remote_api, redirect_stdout, ipaldap,
install_replica_ds, install_dns_records, ntpinstance, ScriptError
install_replica_ds, install_dns_records, ntpinstance, ScriptError,
getargspec
)
@@ -317,7 +317,7 @@ def main():
# Configure dirsrv
with redirect_stdout(ansible_log):
# pylint: disable=deprecated-method
argspec = inspect.getargspec(install_replica_ds)
argspec = getargspec(install_replica_ds)
# pylint: enable=deprecated-method
if "promote" in argspec.args:
ds = install_replica_ds(config, options, ca_enabled,
@@ -343,7 +343,7 @@ def main():
# pylint: enable=deprecated-method
# Always try to install DNS records
# pylint: disable=deprecated-method
argspec = inspect.getargspec(install_dns_records)
argspec = getargspec(install_dns_records)
# pylint: enable=deprecated-method
if "fstore" not in argspec.args:
install_dns_records(config, options, remote_api)

View File

@@ -90,14 +90,13 @@ RETURN = '''
'''
import os
import inspect
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_replica import (
AnsibleModuleLog, setup_logging, installer, DN, paths, sysrestore,
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
gen_ReplicaConfig, gen_remote_api, api, redirect_stdout, create_ipa_conf,
install_http
install_http, getargspec
)
@@ -203,7 +202,7 @@ def main():
master=config.master_host_name)
# pylint: disable=deprecated-method
argspec = inspect.getargspec(install_http)
argspec = getargspec(install_http)
# pylint: enable=deprecated-method
if "promote" in argspec.args:
install_http(

View File

@@ -78,13 +78,12 @@ RETURN = '''
'''
import os
import inspect
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_replica import (
AnsibleModuleLog, setup_logging, installer, DN, paths, sysrestore,
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
gen_ReplicaConfig, api, redirect_stdout, install_krb
gen_ReplicaConfig, api, redirect_stdout, install_krb, getargspec
)
@@ -162,7 +161,7 @@ def main():
with redirect_stdout(ansible_log):
# pylint: disable=deprecated-method
argspec = inspect.getargspec(install_krb)
argspec = getargspec(install_krb)
# pylint: enable=deprecated-method
if "promote" in argspec.args:
install_krb(

View File

@@ -136,7 +136,6 @@ RETURN = '''
'''
import os
import inspect
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_replica import (
@@ -144,7 +143,7 @@ from ansible.module_utils.ansible_ipa_replica import (
ansible_module_get_parsed_ip_addresses, service,
redirect_stdout, create_ipa_conf, ipautil,
x509, validate_domain_name, common_check,
IPA_PYTHON_VERSION
IPA_PYTHON_VERSION, getargspec, adtrustinstance
)
@@ -271,6 +270,14 @@ def main():
# # options.setup_adtrust = False
# # ansible_module.warn(msg="adtrust is not supported, disabling")
sid_generation_always = False
if not options.setup_adtrust:
# pylint: disable=deprecated-method
argspec = getargspec(adtrustinstance.ADTRUSTInstance.__init__)
# pylint: enable=deprecated-method
if "fulltrust" in argspec.args:
sid_generation_always = True
# if options.setup_kra and not kra_imported:
# # if "kra" not in options._allow_missing:
# ansible_module.fail_json(msg="kra can not be imported")
@@ -287,7 +294,7 @@ def main():
# create_ipa_conf has the additional master argument.
change_master_for_certmonger = False
# pylint: disable=deprecated-method
argspec = inspect.getargspec(create_ipa_conf)
argspec = getargspec(create_ipa_conf)
# pylint: enable=deprecated-method
if "master" in argspec.args:
change_master_for_certmonger = True
@@ -421,7 +428,7 @@ def main():
try:
with redirect_stdout(ansible_log):
# pylint: disable=deprecated-method
argspec = inspect.getargspec(common_check)
argspec = getargspec(common_check)
# pylint: enable=deprecated-method
if "skip_mem_check" in argspec.args:
common_check(options.no_ntp, options.skip_mem_check,
@@ -472,6 +479,7 @@ def main():
# additional
client_enrolled=client_enrolled,
change_master_for_certmonger=change_master_for_certmonger,
sid_generation_always=sid_generation_always
)

View File

@@ -46,7 +46,8 @@ __all__ = ["contextlib", "dnsexception", "dnsresolver", "dnsreversename",
"common_check", "current_domain_level",
"check_domain_level_is_supported", "promotion_check_ipa_domain",
"SSSDConfig", "CalledProcessError", "timeconf", "ntpinstance",
"dnsname", "kernel_keyring", "krbinstance"]
"dnsname", "kernel_keyring", "krbinstance", "getargspec",
"adtrustinstance"]
import sys
@@ -59,6 +60,28 @@ else:
import logging
from contextlib import contextmanager as contextlib_contextmanager
# 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)
from ipapython.version import NUM_VERSION, VERSION
if NUM_VERSION < 30201:
@@ -105,6 +128,7 @@ else:
adtrust, bindinstance, ca, certs, dns, dsinstance, httpinstance,
installutils, kra, krbinstance,
otpdinstance, custodiainstance, service, upgradeinstance)
from ipaserver.install import adtrustinstance
try:
from ipaserver.masters import (
find_providing_servers, find_providing_server)

View File

@@ -557,6 +557,7 @@
_subject_base: "{{ result_ipareplica_prepare._subject_base }}"
_pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
_random_serial_numbers: "{{ result_ipareplica_prepare._random_serial_numbers }}"
dirman_password: "{{ ipareplica_dirman_password }}"
config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
config_master_host_name:
@@ -748,13 +749,15 @@
ccache: "{{ result_ipareplica_prepare.ccache }}"
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
setup_adtrust: "{{ result_ipareplica_test.setup_adtrust }}"
config_master_host_name:
"{{ result_ipareplica_prepare.config_master_host_name }}"
adtrust_netbios_name:
"{{ result_ipareplica_prepare.adtrust_netbios_name }}"
adtrust_reset_netbios_name:
"{{ result_ipareplica_prepare.adtrust_reset_netbios_name }}"
when: result_ipareplica_test.setup_adtrust
when: result_ipareplica_test.setup_adtrust or
result_ipareplica_test.sid_generation_always
- name: Install - Enable IPA
ipareplica_enable_ipa:

View File

@@ -141,6 +141,9 @@ options:
setup_ca:
description: Configure a dogtag CA
required: yes
sid_generation_always:
description: Enable SID generation always
required: yes
_hostname_overridden:
description: The installer _hostname_overridden setting
required: yes
@@ -213,6 +216,10 @@ def main():
# additional
setup_ca=dict(required=False, type='bool', default=False),
random_serial_numbers=dict(required=False, type='bool',
default=False),
sid_generation_always=dict(required=False, type='bool',
default=False),
_hostname_overridden=dict(required=False, type='bool',
default=False),
),
@@ -225,9 +232,11 @@ def main():
# initialize return values for flake ############################
# These are set by ca.install_check
# These are set by ca.install_check and need to be passed to ca.install
# in the _setup_ca module and also some others.
options._subject_base = None
options._ca_subject = None
options._random_serial_numbers = False
# set values ####################################################
@@ -277,8 +286,11 @@ def main():
options.netbios_name = ansible_module.params.get('netbios_name')
# additional
options.setup_ca = ansible_module.params.get('setup_ca')
options.random_serial_numbers = ansible_module.params.get(
'random_serial_numbers')
options._host_name_overridden = ansible_module.params.get(
'_hostname_overridden')
sid_generation_always = ansible_module.params.get('sid_generation_always')
options.kasp_db_file = None
# init ##################################################################
@@ -371,7 +383,7 @@ def main():
logger.debug('Starting Directory Server')
services.knownservices.dirsrv.start(instance_name)
if options.setup_adtrust:
if options.setup_adtrust or sid_generation_always:
with redirect_stdout(ansible_log):
adtrust.install_check(False, options, api)
@@ -405,6 +417,7 @@ def main():
_subject_base=options._subject_base,
ca_subject=options.ca_subject,
_ca_subject=options._ca_subject,
_random_serial_numbers=options._random_serial_numbers,
# dns
reverse_zones=options.reverse_zones,
forward_policy=options.forward_policy,

View File

@@ -132,6 +132,9 @@ options:
ca_signing_algorithm:
description: Signing algorithm of the IPA CA certificate
required: yes
_random_serial_numbers:
description: The installer _random_serial_numbers setting
required: yes
reverse_zones:
description: The reverse DNS zones to use
required: yes
@@ -204,6 +207,7 @@ def main():
ca_subject=dict(required=False),
_ca_subject=dict(required=False),
ca_signing_algorithm=dict(required=False),
_random_serial_numbers=dict(required=True, type='bool'),
# dns
reverse_zones=dict(required=False, type='list', default=[]),
no_reverse=dict(required=False, type='bool', default=False),
@@ -259,6 +263,8 @@ def main():
options._ca_subject = ansible_module.params.get('_ca_subject')
options.ca_signing_algorithm = ansible_module.params.get(
'ca_signing_algorithm')
options._random_serial_numbers = ansible_module.params.get(
'_random_serial_numbers')
# dns
options.reverse_zones = ansible_module.params.get('reverse_zones')
options.no_reverse = ansible_module.params.get('no_reverse')

View File

@@ -53,12 +53,11 @@ EXAMPLES = '''
RETURN = '''
'''
import inspect
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_server import (
AnsibleModuleLog, setup_logging, options, sysrestore, paths,
redirect_stdout, time_service, sync_time, ntpinstance, timeconf
redirect_stdout, time_service, sync_time, ntpinstance, timeconf,
getargspec
)
@@ -94,7 +93,7 @@ def main():
ansible_module.log("Synchronizing time")
# pylint: disable=deprecated-method
argspec = inspect.getargspec(sync_time)
argspec = getargspec(sync_time)
# pylint: enable=deprecated-method
if "options" not in argspec.args:
synced_ntp = sync_time(options.ntp_servers, options.ntp_pool,

View File

@@ -212,7 +212,6 @@ RETURN = '''
import os
import sys
import inspect
import random
from shutil import copyfile
@@ -226,7 +225,7 @@ from ansible.module_utils.ansible_ipa_server import (
read_cache, ca, tasks, check_ldap_conf, timeconf, httpinstance,
check_dirsrv, ScriptError, get_fqdn, verify_fqdn, BadHostError,
validate_domain_name, load_pkcs12, IPA_PYTHON_VERSION,
encode_certificate, check_available_memory
encode_certificate, check_available_memory, getargspec, adtrustinstance
)
from ansible.module_utils import six
@@ -395,12 +394,16 @@ def main():
# version specific ######################################################
if options.setup_adtrust and not adtrust_imported:
# if "adtrust" not in options._allow_missing:
ansible_module.fail_json(msg="adtrust can not be imported")
# else:
# options.setup_adtrust = False
# ansible_module.warn(msg="adtrust is not supported, disabling")
sid_generation_always = False
if not options.setup_adtrust:
# pylint: disable=deprecated-method
argspec = getargspec(adtrustinstance.ADTRUSTInstance.__init__)
# pylint: enable=deprecated-method
if "fulltrust" in argspec.args:
sid_generation_always = True
else:
if not adtrust_imported:
ansible_module.fail_json(msg="adtrust can not be imported")
if options.setup_kra and not kra_imported:
# if "kra" not in options._allow_missing:
@@ -522,7 +525,8 @@ def main():
"You cannot specify an --enable-compat option without the "
"--setup-adtrust option")
if self.netbios_name:
# Deactivate test for new IPA SID generation
if self.netbios_name and not sid_generation_always:
raise RuntimeError(
"You cannot specify a --netbios-name option without the "
"--setup-adtrust option")
@@ -944,7 +948,7 @@ def main():
realm_name = options.realm_name.upper()
# pylint: disable=deprecated-method
argspec = inspect.getargspec(validate_domain_name)
argspec = getargspec(validate_domain_name)
# pylint: enable=deprecated-method
if "entity" in argspec.args:
# NUM_VERSION >= 40690:
@@ -1079,7 +1083,8 @@ def main():
ntp_pool=options.ntp_pool,
# additional
_installation_cleanup=_installation_cleanup,
domainlevel=options.domainlevel)
domainlevel=options.domainlevel,
sid_generation_always=sid_generation_always)
if __name__ == '__main__':

View File

@@ -41,7 +41,7 @@ __all__ = ["IPAChangeConf", "certmonger", "sysrestore", "root_logger",
"adtrustinstance", "IPAAPI_USER", "sync_time", "PKIIniLoader",
"default_subject_base", "default_ca_subject_dn",
"check_ldap_conf", "encode_certificate", "decode_certificate",
"check_available_memory"]
"check_available_memory", "getargspec"]
import sys
@@ -58,6 +58,28 @@ else:
from ansible.module_utils import six
import base64
# 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)
from ipapython.version import NUM_VERSION, VERSION
if NUM_VERSION < 30201:

View File

@@ -11,4 +11,4 @@
force: yes
- name: Install - Extend ipaserver_external_cert_files with "/root/{{ item | basename }}"
set_fact:
ipaserver_external_cert_files: "{{ ipaserver_external_cert_files }} + [ '/root/{{ item | basename }}' ]"
ipaserver_external_cert_files: "{{ ipaserver_external_cert_files + [ '/root/' + (item | basename) ] }}"

View File

@@ -191,6 +191,8 @@
secondary_rid_base: "{{ ipaserver_secondary_rid_base | default(omit) }}"
### additional ###
setup_ca: "{{ result_ipaserver_test.setup_ca }}"
sid_generation_always: "{{ result_ipaserver_test.sid_generation_always }}"
random_serial_numbers: no
_hostname_overridden: "{{ result_ipaserver_test._hostname_overridden }}"
register: result_ipaserver_prepare
@@ -298,6 +300,7 @@
_ca_subject: "{{ result_ipaserver_prepare._ca_subject }}"
ca_signing_algorithm: "{{ ipaserver_ca_signing_algorithm |
default(omit) }}"
_random_serial_numbers: "{{ result_ipaserver_prepare._random_serial_numbers }}"
reverse_zones: "{{ result_ipaserver_prepare.reverse_zones }}"
no_reverse: "{{ ipaserver_no_reverse }}"
auto_forwarders: "{{ ipaserver_auto_forwarders }}"
@@ -392,7 +395,8 @@
adtrust_netbios_name: "{{ result_ipaserver_prepare.adtrust_netbios_name }}"
adtrust_reset_netbios_name:
"{{ result_ipaserver_prepare.adtrust_reset_netbios_name }}"
when: result_ipaserver_test.setup_adtrust
when: result_ipaserver_test.setup_adtrust or
result_ipaserver_test.sid_generation_always
- name: Install - Set DS password
ipaserver_set_ds_password:

View File

@@ -0,0 +1,111 @@
ipasmartcard_client role
========================
Description
-----------
This role allows to configure IPA clients for Smart Card authentication.
**Note**: The ansible-freeipa smartcard client role requires an enrolled IPA client.
Features
--------
* Client setup for Smart Card authentication
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.5 and up are supported by this role.
Supported Distributions
-----------------------
* RHEL/CentOS 7.6+
* Fedora 26+
Requirements
------------
**Controller**
* Ansible version: 2.8+
**Node**
* Supported FreeIPA version (see above)
* Supported distribution (needed for package installation only, see above)
* Enrolled IPA client
Limitations
-----------
Only the enablement of smartcards is supported by the role, there is no disablement.
Usage
=====
Example inventory file with IPA clients:
```ini
[ipaclients]
ipaclient1.example.com
ipaclient2.example.com
[ipaclients:vars]
ipaadmin_password=SomeADMINpassword
ipasmartcard_client_ca_certs=/etc/ipa/ca.crt
```
Example playbook to setup smartcard for the IPA clients using admin password and ipasmartcard_client_ca_certs from inventory file:
```yaml
---
- name: Playbook to setup smartcard for IPA clients
hosts: ipaclients
become: true
roles:
- role: ipasmartcard_client
state: present
```
Playbooks
=========
The playbooks needed to setup smartcard for the IPA clients is part of the repository in the playbooks folder.
```
install-smartcard-clients.yml
```
Please remember to link or copy the playbooks to the base directory of ansible-freeipa if you want to use the roles within the source archive.
How to setup smartcard for clients
----------------------------------
```bash
ansible-playbook -v -i inventory/hosts install-smartcard-clients.yml
```
This will setup the clients for smartcard use.
Variables
=========
Variable | Description | Required
-------- | ----------- | --------
`ipaadmin_principal` | The kerberos principal used for admin. Will be set to `admin` if not set. (string) | no
`ipaadmin_password` | The password for the IPA admin user. As an alternative an admin user keytab can be used instead with `ipaadmin_keytab`. (string) | yes
`ipaadmin_keytab` | The admin keytab as an alternative to `ipaadmin_password`. (string) | no
`ipasmartcard_client_ca_certs` | The CA certificates for smartcard use. If `ipasmartcard_client_ca_certs` is not set, but `ipasmartcard_server_ca_certs`, then `ipasmartcard_server_ca_certs` will be used. | yes
Authors
=======
Thomas Woerner

View File

@@ -0,0 +1,4 @@
---
# defaults file for ipasmartcard_client role
ipaclient_install_packages: yes

View File

@@ -0,0 +1,30 @@
#!/bin/bash -eu
# 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/>.
cert_file=$1
db=$2
if [ -z "${cert_file}" ] || [ -z "${db}" ]; then
echo "Usage: $0 <ca cert> <db file>"
exit 1
fi
cat "${cert_file}" >> "${db}"

View File

@@ -0,0 +1,31 @@
#!/bin/bash -eu
# 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/>.
cert_file=$1
db=$2
if [ -z "${cert_file}" ] || [ -z "${db}" ]; then
echo "Usage: $0 <ca cert> <db file>"
exit 1
fi
uuid=$(uuidgen)
certutil -d "${db}" -A -i "${cert_file}" -n "Smart Card CA ${uuid}" -t CT,C,C

View File

@@ -0,0 +1,36 @@
#!/bin/bash -eu
# 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/>.
nssdb=$1
module_name="OpenSC"
pkcs11_shared_lib="/usr/lib64/opensc-pkcs11.so"
if [ -z "${nssdb}" ]; then
echo "Usage: $0 <nssdb>"
exit 1
fi
if modutil -dbdir "${nssdb}" -list | grep -q "${module_name}" || p11-kit list-modules | grep -i "${module_name}" -q
then
echo "${module_name} PKCS#11 module already configured"
else
echo "" | modutil -dbdir "${nssdb}" -add "${module_name}" -libfile "${pkcs11_shared_lib}"
fi

View File

@@ -0,0 +1,83 @@
# -*- 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: ipasmartcard_client_get_vars
short description:
Get variables from ipaplatform and python interpreter used for the module.
description:
Get variables from ipaplatform and python interpreter used for the module.
options:
author:
- Thomas Woerner
'''
EXAMPLES = '''
- name: Get VARS from IPA
ipasmartcard_client_get_vars:
register: ipasmartcard_client_vars
'''
RETURN = '''
NSS_DB_DIR:
description: paths.NSS_DB_DIR from ipaplatform
returned: always
type: str
USE_AUTHSELECT:
description: True if "AUTHSELECT" is defined in paths
returned: always
type: bool
python_interpreter:
description: Python interpreter from sys.executable
returned: always
type: str
'''
import sys
from ansible.module_utils.basic import AnsibleModule
from ipaplatform.paths import paths
def main():
ansible_module = AnsibleModule(
argument_spec={},
supports_check_mode=False,
)
ansible_module.exit_json(changed=False,
NSS_DB_DIR=paths.NSS_DB_DIR,
USE_AUTHSELECT=hasattr(paths, "AUTHSELECT"),
python_interpreter=sys.executable)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
# Authors:
# Thomas Woerner <twoerner@redhat.com>
#
# Based on ipa-replica-install code
#
# 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: ipasmartcard_server_validate_ca_certs
short description: Validate CA certs
description: Validate CA certs
options:
ca_cert_files:
description:
List of files containing CA certificates for the service certificate
files
required: yes
author:
- Thomas Woerner
'''
EXAMPLES = '''
'''
RETURN = '''
'''
import os.path
from ansible.module_utils.basic import AnsibleModule
try:
from ipalib import x509
except ImportError:
x509 = None
def main():
ansible_module = AnsibleModule(
argument_spec=dict(
ca_cert_files=dict(required=False, type='list', default=[]),
),
supports_check_mode=False,
)
# get parameters #
ca_cert_files = ansible_module.params.get('ca_cert_files')
# import check #
if x509 is None:
ansible_module.fail_json(msg="Failed to import x509 from ipalib")
# validate ca certs #
if ca_cert_files is not None:
if not isinstance(ca_cert_files, list):
ansible_module.fail_json(
msg="Expected list, got %s" % repr(ca_cert_files))
# remove duplicates
ca_cert_files = list(dict.fromkeys(ca_cert_files))
# validate
for cert in ca_cert_files:
if not os.path.exists(cert):
ansible_module.fail_json(msg="'%s' does not exist" % cert)
if not os.path.isfile(cert):
ansible_module.fail_json(msg="'%s' is not a file" % cert)
if not os.path.isabs(cert):
ansible_module.fail_json(
msg="'%s' is not an absolute file path" % cert)
try:
x509.load_certificate_from_file(cert)
except Exception:
ansible_module.fail_json(
msg="'%s' is not a valid certificate file" % cert)
# exit #
ansible_module.exit_json(changed=False,
ca_cert_files=ca_cert_files)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,22 @@
---
dependencies: []
galaxy_info:
author: Thomas Woerner
description: A role to setup IPA server(s) for Smart Card authentication
company: Red Hat, Inc
license: GPLv3
min_ansible_version: 2.8
platforms:
- name: Fedora
versions:
- all
- name: EL
versions:
- 7
- 8
galaxy_tags:
- identity
- ipa
- freeipa
- smartcard

View File

@@ -0,0 +1,173 @@
---
# tasks file for ipasmartcard_client role
- name: Uninstall smartcard client
ansible.builtin.fail: msg="Uninstalling smartcard for IPA is not supported"
when: state|default('present') == 'absent'
- name: Import variables specific to distribution
ansible.builtin.include_vars: "{{ item }}"
with_first_found:
- "vars/{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_version'] }}.yml"
- "vars/{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_major_version'] }}.yml"
- "vars/{{ ansible_facts['distribution'] }}.yml"
# os_family is used as a fallback for distros which are not currently
# supported, but are based on a supported distro family. For example,
# Oracle, Rocky, Alma and Alibaba linux, which are all "RedHat" based.
- "vars/{{ ansible_facts['os_family'] }}-{{ ansible_facts['distribution_version'] }}.yml"
- "vars/{{ ansible_facts['os_family'] }}-{{ ansible_facts['distribution_major_version'] }}.yml"
- "vars/{{ ansible_facts['os_family'] }}.yml"
# If neither distro nor family is supported, try a default configuration.
- "vars/default.yml"
- block:
# CA CERTS
# Use "ipasmartcard_server_ca_certs"
- name: Use "ipasmartcard_server_ca_certs"
ansible.builtin.set_fact:
ipasmartcard_client_ca_certs: "{{ ipasmartcard_server_ca_certs }}"
when: ipasmartcard_client_ca_certs is not defined and
ipasmartcard_server_ca_certs is defined
# Fail on empty "ipasmartcard_client_ca_certs"
- name: Fail on empty "ipasmartcard_client_ca_certs"
ansible.builtin.fail: msg="No CA certs given in 'ipasmartcard_client_ca_certs'"
when: ipasmartcard_client_ca_certs is not defined or
ipasmartcard_client_ca_certs | length < 1
# Validate ipasmartcard_client_ca_certs
- name: Validate CA certs "{{ ipasmartcard_client_ca_certs }}"
ipasmartcard_client_validate_ca_certs:
ca_cert_files: "{{ ipasmartcard_client_ca_certs }}"
register: result_validate_ca_certs
# INSTALL needed packages: opensc, dconf and krb5-pkinit-openssl
- name: Ensure needed packages are installed
ansible.builtin.package:
name: "{{ ipasmartcard_client_packages }}"
state: present
# REMOVE pam_pkcs11
- name: Ensure pam_pkcs11 is missing
ansible.builtin.package:
name: "{{ ipasmartcard_client_remove_pam_pkcs11_packages }}"
state: absent
# KINIT
- name: Set default principal if not given
ansible.builtin.set_fact:
ipaadmin_principal: admin
when: ipaadmin_principal is undefined
- name: kinit using "{{ ipaadmin_principal }}" password
ansible.builtin.command: kinit "{{ ipaadmin_principal }}"
args:
stdin: "{{ ipaadmin_password }}"
when: ipaadmin_password is defined
- name: kinit using "{{ ipaadmin_principal }}" keytab
ansible.builtin.command: kinit -kt "{{ ipaadmin_keytab }}" "{{ ipaadmin_principal }}"
when: ipaadmin_keytab is defined
# Enable and start smartcard daemon
- name: Enable and start smartcard daemon
ansible.builtin.service:
name: pcscd
enabled: true
state: started
# GET VARS FROM IPA
- name: Get VARS from IPA
ipasmartcard_client_get_vars:
register: ipasmartcard_client_vars
# Add pkcs11 module to systemwide db
- name: Add pkcs11 module to systemwide db
ansible.builtin.script: ipasmartcard_client_add_pkcs11_module_to_systemwide_db.sh
"{{ ipasmartcard_client_vars.NSS_DB_DIR }}"
# Ensure /etc/sssd/pki exists
- block:
- name: Ensure /etc/sssd/pki exists
ansible.builtin.file:
path: /etc/sssd/pki
state: directory
mode: 0711
- name: Ensure /etc/sssd/pki/sssd_auth_ca_db.pem is absent
ansible.builtin.file:
path: /etc/sssd/pki/sssd_auth_ca_db.pem
state: absent
when: ipasmartcard_client_vars.USE_AUTHSELECT
# Upload smartcard CA certificates to systemwide db
- name: Upload smartcard CA certificates to systemwide db
ansible.builtin.script: ipasmartcard_client_add_ca_to_systemwide_db.sh
"{{ item }}"
"{{ ipasmartcard_client_vars.NSS_DB_DIR }}"
with_items: "{{ result_validate_ca_certs.ca_cert_files }}"
# Newer version of sssd use OpenSSL and read the CA certs
# from /etc/sssd/pki/sssd_auth_ca_db.pem
- name: Add CA certs to /etc/sssd/pki/sssd_auth_ca_db.pem
ansible.builtin.script: ipasmartcard_client_add_ca_to_sssd_auth_ca_db.sh
"{{ item }}"
/etc/sssd/pki/sssd_auth_ca_db.pem
with_items: "{{ result_validate_ca_certs.ca_cert_files }}"
when: ipasmartcard_client_vars.USE_AUTHSELECT
# Update ipa CA certificate store
- name: Update ipa CA certificate store
ansible.builtin.command: ipa-certupdate
# Run authselect or authconfig to configure smartcard auth
- name: Use authselect to enable Smart Card authentication
ansible.builtin.command: authselect enable-feature with-smartcard
when: ipasmartcard_client_vars.USE_AUTHSELECT
- name: Use authconfig to enable Smart Card authentication
ansible.builtin.command: authconfig --enablesssd --enablesssdauth --enablesmartcard --smartcardmodule=sssd --smartcardaction=1 --updateall
when: not ipasmartcard_client_vars.USE_AUTHSELECT
# Set pam_cert_auth=True in /etc/sssd/sssd.conf
- name: Store NSS OCSP upgrade state
ansible.builtin.command: "{{ ipasmartcard_client_vars.python_interpreter }}"
args:
stdin: |
from SSSDConfig import SSSDConfig
c = SSSDConfig()
c.import_config()
c.set("pam", "pam_cert_auth", "True")
c.write()
when: ipasmartcard_client_vars.USE_AUTHSELECT
# Restart sssd
- name: Restart sssd
ansible.builtin.service:
name: sssd
state: restarted
### ALWAYS ###
always:
- name: kdestroy
ansible.builtin.command: kdestroy -A

View File

@@ -0,0 +1,3 @@
---
ipasmartcard_client_remove_pam_pkcs11_packages: [ "pam_pkcs11" ]
ipasmartcard_client_packages: [ "opensc", "dconf", "krb5-pkinit-openssl" ]

View File

@@ -0,0 +1,169 @@
ipasmartcard_server role
========================
Description
-----------
This role allows to configure an IPA server (master or replica) for Smart Card authentication.
**Note**: The ansible-freeipa smartcard server role requires a configured IPA server with ipa-ca.DOMAIN resolvable by the DNS server.
With external DNS ipa-ca.DOMAIN needs to be set.
Features
--------
* Server setup for Smart Card authentication
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.5 and up are supported by this role.
Supported Distributions
-----------------------
* RHEL/CentOS 7.6+
* Fedora 26+
Requirements
------------
**Controller**
* Ansible version: 2.8+
**Node**
* Supported FreeIPA version (see above)
* Supported distribution (needed for package installation only, see above)
* Deployed IPA server
Limitations
-----------
Only the enablement of smartcards is supported by the role, there is no disablement. The disablement of features in IPA in not supported.
Usage
=====
Example inventory file with ipa server and replicas:
```ini
[ipaserver]
ipaserver.example.com
[ipareplicas]
ipareplica1.example.com
ipareplica2.example.com
[ipacluster:children]
ipaserver
ipareplicas
[ipacluster:vars]
ipaadmin_password=SomeADMINpassword
ipasmartcard_server_ca_certs=/etc/ipa/ca.crt
```
Example playbook to setup smartcard for the IPA server using admin password and ipasmartcard_server_ca_certs from inventory file:
```yaml
---
- name: Playbook to setup smartcard for IPA server
hosts: ipaserver
become: true
roles:
- role: ipasmartcard_server
state: present
```
Example playbook to setup smartcard for the IPA servers in ipareplicas group using admin password and ipasmartcard_server_ca_certs from inventory file:
```yaml
---
- name: Playbook to setup smartcard for IPA replicas
hosts: ipareplicas
become: true
roles:
- role: ipasmartcard_server
state: present
```
Example playbook to setup smartcard for the IPA servers in ipaserver and ipareplicas group using admin password and ipasmartcard_server_ca_certs from inventory file:
```yaml
---
- name: Playbook to setup smartcard for IPA server and replicas
hosts: ipaserver, ipareplicas
become: true
roles:
- role: ipasmartcard_server
state: present
```
Playbooks
=========
The playbooks needed to setup smartcard for the IPA server and the replicas are part of the repository in the playbooks folder.
```
install-smartcard-server.yml
install-smartcard-servers.yml
install-smartcard-replicas.yml
```
Please remember to link or copy the playbooks to the base directory of ansible-freeipa if you want to use the roles within the source archive.
How to setup smartcard for server
---------------------------------
```bash
ansible-playbook -v -i inventory/hosts install-smartcard-server.yml
```
This will setup the server for smartcard use.
How to setup smartcard for replicas
-----------------------------------
```bash
ansible-playbook -v -i inventory/hosts install-smartcard-replicas.yml
```
This will setup the replicas for smartcard use.
How to setup smartcard for server and replicas
----------------------------------------------
```bash
ansible-playbook -v -i inventory/hosts install-smartcard-servers.yml
```
This will setup the replicas for smartcard use.
Variables
=========
Variable | Description | Required
-------- | ----------- | --------
`ipaadmin_principal` | The kerberos principal used for admin. Will be set to `admin` if not set. (string) | no
`ipaadmin_password` | The password for the IPA admin user. As an alternative an admin user keytab can be used instead with `ipaadmin_keytab`. (string) | yes
`ipaadmin_keytab` | The admin keytab as an alternative to `ipaadmin_password`. (string) | no
`ipaserver_hostname` | Fully qualified name of the server. By default `ansible_facts['fqdn']` will be used. (string) | no
`ipaserver_domain` | The primary DNS domain of an existing IPA deployment. By default the domain will be used from ipa server-find result. (string) | no
`ipasmartcard_server_ca_certs` | The CA certificates for smartcard use. (list of string) | yes
Authors
=======
Thomas Woerner

View File

@@ -0,0 +1,4 @@
---
# defaults file for ipasmartcard_server role
ipaserver_install_packages: yes

View File

@@ -0,0 +1,30 @@
#!/bin/bash -eu
# 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/>.
cert_file=$1
db=$2
if [ -z "${cert_file}" ] || [ -z "${db}" ]; then
echo "Usage: $0 <ca cert> <db file>"
exit 1
fi
cat "${cert_file}" >> "${db}"

View File

@@ -0,0 +1,31 @@
#!/bin/bash -eu
# 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/>.
cert_file=$1
db=$2
if [ -z "${cert_file}" ] || [ -z "${db}" ]; then
echo "Usage: $0 <ca cert> <db file>"
exit 1
fi
uuid=$(uuidgen)
certutil -d "${db}" -A -i "${cert_file}" -n "Smart Card CA ${uuid}" -t CT,C,C

View File

@@ -0,0 +1,35 @@
#!/bin/bash -eu
# 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/>.
directive=$1
conf_file=$2
if [ -z "${directive}" ] || [ -z "${conf_file}" ]; then
echo "Usage: $0 <directive> <config file>"
exit 1
fi
if grep -q "${directive} " "${conf_file}"
then
sed -i.ipabkp -r "s/^#*[[:space:]]*${directive}[[:space:]]+(on|off)$/${directive} on/" "${conf_file}"
else
sed -i.ipabkp "/<\/VirtualHost>/i ${directive} on" "${conf_file}"
fi

View File

@@ -0,0 +1,35 @@
#!/bin/bash -eu
# 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/>.
directive=$1
nss_conf=$2
nickname=$3
alias_dir=$4
if [ -z "${directive}" ] || [ -z "${nss_conf}" ] || [ -z "${nickname}" ] ||
[ -z "${alias_dir}" ]
then
echo "Usage: $0 <directive> <nss conf> <nickname directive> <alias directory>"
exit 1
fi
http_cert_nick=$(grep "${nickname}" "${nss_conf}" | cut -f 2 -d ' ')
certutil -M -n "$http_cert_nick" -d "${alias_dir}" -f "${alias_dir}/pwdfile.txt" -t "Pu,u,u"

View File

@@ -0,0 +1,159 @@
# -*- 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: ipasmartcard_server_get_vars
short description:
Get variables from ipaplatform and ipaserver and python interpreter.
description:
Get variables from ipaplatform and ipaserver and python interpreter.
options:
author:
- Thomas Woerner
'''
EXAMPLES = '''
- name: Get VARS from IPA
ipasmartcard_server_get_vars:
register: ipasmartcard_server_vars
'''
RETURN = '''
NSS_OCSP_ENABLED:
description:
Empty string for newer systems using ssl.conf and not nss.conf for
HTTP instance where OCSP_ENABLED and OCSP_DIRECTIVE are defined in
ipaserver.install.httpinstance, else NSS_OCSP_ENABLED imported from
ipaserver.install.httpinstance.
returned: always
type: str
NSS_OCSP_DIRECTIVE:
description:
Empty string for newer systems using ssl.conf and not nss.conf for
HTTP instance where OCSP_ENABLED and OCSP_DIRECTIVE are defined in
ipaserver.install.httpinstance, else NSSOCSP.
returned: always
type: str
NSS_NICKNAME_DIRECTIVE:
description:
Empty string for newer systems using ssl.conf and not nss.conf for
HTTP instance where OCSP_ENABLED and OCSP_DIRECTIVE are defined in
ipaserver.install.httpinstance, else NSSNickname
returned: always
type: str
OCSP_ENABLED:
description:
OCSP_ENABLED imported from ipaserver.install.httpinstance, if import
succeeds, else ""
returned: always
type: str
OCSP_DIRECTIVE:
description:
OCSP_DIRECTIVE imported from ipaserver.install.httpinstance, if import
succeeds, else ""
returned: always
type: str
HTTPD_SSL_CONF:
description: paths.HTTPD_SSL_CONF from ipaplatform
returned: always
type: str
HTTPD_NSS_CONF:
description: paths.HTTPD_NSS_CONF from ipaplatform
returned: always
type: str
HTTPD_ALIAS_DIR:
description: paths.HTTPD_ALIAS_DIR from ipaplatform
returned: always
type: str
allow_httpd_ifp:
description:
True if sssd_enable_ifp can be imported from ipaclient.install.client,
else false.
returned: always
type: bool
NSS_DB_DIR:
description: paths.NSS_DB_DIR from ipaplatform
returned: always
type: str
USE_AUTHSELECT:
description: True if "AUTHSELECT" is defined in paths
returned: always
type: bool
python_interpreter:
description: Python interpreter from sys.executable
returned: always
type: str
'''
import sys
from ansible.module_utils.basic import AnsibleModule
from ipaplatform.paths import paths
try:
from ipaserver.install.httpinstance import OCSP_ENABLED, OCSP_DIRECTIVE
NSS_OCSP_ENABLED = ""
NSS_OCSP_DIRECTIVE = ""
NSS_NICKNAME_DIRECTIVE = ""
except ImportError:
from ipaserver.install.httpinstance import NSS_OCSP_ENABLED
NSS_OCSP_DIRECTIVE = "NSSOCSP"
NSS_NICKNAME_DIRECTIVE = "NSSNickname"
OCSP_ENABLED = ""
OCSP_DIRECTIVE = ""
try:
from ipaclient.install.client import sssd_enable_ifp
except ImportError:
sssd_enable_ifp = None
def main():
ansible_module = AnsibleModule(
argument_spec={},
supports_check_mode=False,
)
ansible_module.exit_json(changed=False,
NSS_OCSP_ENABLED=NSS_OCSP_ENABLED,
NSS_OCSP_DIRECTIVE=NSS_OCSP_DIRECTIVE,
NSS_NICKNAME_DIRECTIVE=NSS_NICKNAME_DIRECTIVE,
OCSP_ENABLED=OCSP_ENABLED,
OCSP_DIRECTIVE=OCSP_DIRECTIVE,
HTTPD_SSL_CONF=paths.HTTPD_SSL_CONF,
HTTPD_NSS_CONF=paths.HTTPD_NSS_CONF,
HTTPD_ALIAS_DIR=paths.HTTPD_ALIAS_DIR,
allow_httpd_ifp=sssd_enable_ifp is not None,
NSS_DB_DIR=paths.NSS_DB_DIR,
USE_AUTHSELECT=hasattr(paths, "AUTHSELECT"),
python_interpreter=sys.executable)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
# Authors:
# Thomas Woerner <twoerner@redhat.com>
#
# Based on ipa-replica-install code
#
# 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: ipasmartcard_server_validate_ca_certs
short description: Validate CA certs
description: Validate CA certs
options:
ca_cert_files:
description:
List of files containing CA certificates for the service certificate
files
required: yes
author:
- Thomas Woerner
'''
EXAMPLES = '''
'''
RETURN = '''
'''
import os.path
from ansible.module_utils.basic import AnsibleModule
try:
from ipalib import x509
except ImportError:
x509 = None
def main():
ansible_module = AnsibleModule(
argument_spec=dict(
ca_cert_files=dict(required=False, type='list', default=[]),
),
supports_check_mode=False,
)
# get parameters #
ca_cert_files = ansible_module.params.get('ca_cert_files')
# import check #
if x509 is None:
ansible_module.fail_json(msg="Failed to import x509 from ipalib")
# validate ca certs #
if ca_cert_files is not None:
if not isinstance(ca_cert_files, list):
ansible_module.fail_json(
msg="Expected list, got %s" % repr(ca_cert_files))
# remove duplicates
ca_cert_files = list(dict.fromkeys(ca_cert_files))
# validate
for cert in ca_cert_files:
if not os.path.exists(cert):
ansible_module.fail_json(msg="'%s' does not exist" % cert)
if not os.path.isfile(cert):
ansible_module.fail_json(msg="'%s' is not a file" % cert)
if not os.path.isabs(cert):
ansible_module.fail_json(
msg="'%s' is not an absolute file path" % cert)
try:
x509.load_certificate_from_file(cert)
except Exception:
ansible_module.fail_json(
msg="'%s' is not a valid certificate file" % cert)
# exit #
ansible_module.exit_json(changed=False,
ca_cert_files=ca_cert_files)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,22 @@
---
dependencies: []
galaxy_info:
author: Thomas Woerner
description: A role to setup IPA server(s) for Smart Card authentication
company: Red Hat, Inc
license: GPLv3
min_ansible_version: 2.8
platforms:
- name: Fedora
versions:
- all
- name: EL
versions:
- 7
- 8
galaxy_tags:
- identity
- ipa
- freeipa
- smartcard

View File

@@ -0,0 +1,247 @@
---
# tasks file for ipasmartcard_server role
- name: Uninstall smartcard server
ansible.builtin.fail: msg="Uninstalling smartcard for IPA is not supported"
when: state|default('present') == 'absent'
- name: Import variables specific to distribution
ansible.builtin.include_vars: "{{ item }}"
with_first_found:
- "vars/{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_version'] }}.yml"
- "vars/{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_major_version'] }}.yml"
- "vars/{{ ansible_facts['distribution'] }}.yml"
# os_family is used as a fallback for distros which are not currently
# supported, but are based on a supported distro family. For example,
# Oracle, Rocky, Alma and Alibaba linux, which are all "RedHat" based.
- "vars/{{ ansible_facts['os_family'] }}-{{ ansible_facts['distribution_version'] }}.yml"
- "vars/{{ ansible_facts['os_family'] }}-{{ ansible_facts['distribution_major_version'] }}.yml"
- "vars/{{ ansible_facts['os_family'] }}.yml"
# If neither distro nor family is supported, try a default configuration.
- "vars/default.yml"
- block:
# CA CERTS
# Fail on empty "ipasmartcard_server_ca_certs"
- name: Fail on empty "ipasmartcard_server_ca_certs"
ansible.builtin.fail: msg="No CA certs given in 'ipasmartcard_server_ca_certs'"
when: ipasmartcard_server_ca_certs is not defined or
ipasmartcard_server_ca_certs | length < 1
# Validate ipasmartcard_server_ca_certs
- name: Validate CA certs "{{ ipasmartcard_server_ca_certs }}"
ipasmartcard_server_validate_ca_certs:
ca_cert_files: "{{ ipasmartcard_server_ca_certs }}"
register: result_validate_ca_certs
# INSTALL bind-utils
- name: Ensure {{ ipasmartcard_server_bindutils_packages }} are installed
ansible.builtin.package:
name: "{{ ipasmartcard_server_bindutils_packages }}"
state: present
when: ipaserver_install_packages | bool
# KINIT
- name: Set default principal if not given
ansible.builtin.set_fact:
ipaadmin_principal: admin
when: ipaadmin_principal is undefined
- name: kinit using "{{ ipaadmin_principal }}" password
ansible.builtin.command: kinit "{{ ipaadmin_principal }}"
args:
stdin: "{{ ipaadmin_password }}"
when: ipaadmin_password is defined
- name: kinit using "{{ ipaadmin_principal }}" keytab
ansible.builtin.command: kinit -kt "{{ ipaadmin_keytab }}" "{{ ipaadmin_principal }}"
when: ipaadmin_keytab is defined
# IS MASTER
- name: Check that this is an IPA master
ansible.builtin.command: ipa server-show --raw "{{ ipaserver_hostname | default(ansible_facts['fqdn']) }}"
register: result_ipa_server_show
- name: Fail if not an IPA server
ansible.builtin.fail: msg="Not an IPA server"
when: result_ipa_server_show.failed
- name: Get Domain from server-find server name
ansible.builtin.set_fact:
ipaserver_domain: "{{ (result_ipa_server_show.stdout | regex_search('cn: (.+)', '\\1'))[0].split('.')[1:] | join ('.') }}"
when: ipaserver_domain is not defined
- name: Get ipa-ca records
ansible.builtin.command: "dig +short ipa-ca.{{ ipaserver_domain }}"
register: result_get_ipaca_records
- name: Fail if ipa-ca records are not resolvable
ansible.builtin.fail: msg="ipa-ca records are not resolvable"
when: result_get_ipaca_records.failed or
result_get_ipaca_records.stdout | length == 0
# GET VARS FROM IPA
- name: Get VARS from IPA
ipasmartcard_server_get_vars:
register: ipasmartcard_server_vars
# ENABLE NSS OCSP
- name: Enable the OCSP directive in nss.conf
ansible.builtin.script: ipasmartcard_server_enable_ocsp_directive.sh
"{{ ipasmartcard_server_vars.NSS_OCSP_DIRECTIVE }}"
"{{ ipasmartcard_server_vars.HTTPD_NSS_CONF }}"
when: ipasmartcard_server_vars.NSS_OCSP_ENABLED | length > 0
# MARK NSS HTTPD CERT AS TRUSTED
- name: Mark HTTPD CERT as trusted
ansible.builtin.script: ipasmartcard_server_mark_httpd_cert_as_trusted.sh
"{{ ipasmartcard_server_vars.NSS_OCSP_DIRECTIVE }}"
"{{ ipasmartcard_server_vars.HTTPD_NSS_CONF }}"
"{{ ipasmartcard_server_vars.NSS_NICKNAME_DIRECTIVE }}"
"{{ ipasmartcard_server_vars.HTTPD_ALIAS_DIR }}"
when: ipasmartcard_server_vars.NSS_OCSP_ENABLED | length > 0
# ENABLE SSL OCSP
- name: Enable the OCSP directive in ssl.conf
ansible.builtin.script: ipasmartcard_server_enable_ocsp_directive.sh
"{{ ipasmartcard_server_vars.OCSP_DIRECTIVE }}"
"{{ ipasmartcard_server_vars.HTTPD_SSL_CONF }}"
when: ipasmartcard_server_vars.OCSP_ENABLED | length > 0
# Restart apache
- name: Restart apache
ansible.builtin.service:
name: httpd
state: restarted
# RECORD HTTPD OCSP STATUS
# Store the NSS OCSP upgrade state
- name: Store NSS OCSP upgrade state
ansible.builtin.command: "{{ ipasmartcard_server_vars.python_interpreter }}"
args:
stdin: |
from ipaserver.install import sysupgrade
sysupgrade.set_upgrade_state("httpd", "{{ ipasmartcard_server_vars.NSS_OCSP_DIRECTIVE }}", True)
when: ipasmartcard_server_vars.NSS_OCSP_ENABLED | length > 0
# Store the SSL OCSP upgrade state
- name: Store SSL OCSP upgrade state
ansible.builtin.command: "{{ ipasmartcard_server_vars.python_interpreter }}"
args:
stdin: |
from ipaserver.install import sysupgrade
sysupgrade.set_upgrade_state("httpd", "{{ ipasmartcard_server_vars.OCSP_DIRECTIVE }}", True)
when: ipasmartcard_server_vars.OCSP_ENABLED | length > 0
# check whether PKINIT is configured on the master
- name: Enable PKINIT
ansible.builtin.command: ipa-pkinit-manage enable
# Enable OK-AS-DELEGATE flag on the HTTP principal
# This enables smart card login to WebUI
- name: Enable OK-AS-DELEGATE flag on the HTTP principal
ipaservice:
name: "HTTP/{{ ipaserver_hostname | default(ansible_facts['fqdn']) }}"
ok_to_auth_as_delegate: yes
# HTTPD IFP
- block:
# Allow Apache to access SSSD IFP
- name: Allow Apache to access SSSD IFP
ansible.builtin.command: "{{ ipasmartcard_server_vars.python_interpreter }}"
args:
stdin: |
import SSSDConfig
from ipaclient.install.client import sssd_enable_ifp
from ipaplatform.paths import paths
c = SSSDConfig.SSSDConfig()
c.import_config()
sssd_enable_ifp(c, allow_httpd=True)
c.write(paths.SSSD_CONF)
when: ipasmartcard_server_vars.OCSP_ENABLED | length > 0
# Restart sssd
- name: Restart sssd
ansible.builtin.service:
name: sssd
state: restarted
when: ipasmartcard_server_vars.allow_httpd_ifp
# Ensure /etc/sssd/pki exists
- block:
- name: Ensure /etc/sssd/pki exists
ansible.builtin.file:
path: /etc/sssd/pki
state: directory
mode: 0711
- name: Ensure /etc/sssd/pki/sssd_auth_ca_db.pem is absent
ansible.builtin.file:
path: /etc/sssd/pki/sssd_auth_ca_db.pem
state: absent
when: ipasmartcard_server_vars.USE_AUTHSELECT
# Upload smartcard CA certificates to systemwide db
- name: Upload smartcard CA certificates to systemwide db
ansible.builtin.script: ipasmartcard_server_add_ca_to_systemwide_db.sh
"{{ item }}"
"{{ ipasmartcard_server_vars.NSS_DB_DIR }}"
with_items: "{{ result_validate_ca_certs.ca_cert_files }}"
# Newer version of sssd use OpenSSL and read the CA certs
# from /etc/sssd/pki/sssd_auth_ca_db.pem
- name: Add CA certs to /etc/sssd/pki/sssd_auth_ca_db.pem
ansible.builtin.script: ipasmartcard_server_add_ca_to_sssd_auth_ca_db.sh
"{{ item }}"
/etc/sssd/pki/sssd_auth_ca_db.pem
with_items: "{{ result_validate_ca_certs.ca_cert_files }}"
when: ipasmartcard_server_vars.USE_AUTHSELECT
# Install smartcard signing CA certs
- name: Install smartcard signing CA certs
ansible.builtin.command: ipa-cacert-manage install "{{ item }}" -t CT,C,C
with_items: "{{ result_validate_ca_certs.ca_cert_files }}"
# Update ipa CA certificate store
- name: Update ipa CA certificate store
ansible.builtin.command: ipa-certupdate
# Restart krb5kdc
- name: Restart krb5kdc
ansible.builtin.service:
name: krb5kdc
state: restarted
### ALWAYS ###
always:
- name: kdestroy
ansible.builtin.command: kdestroy -A

View File

@@ -0,0 +1,2 @@
---
ipasmartcard_server_bindutils_packages: [ "bind-utils" ]

View File

@@ -71,7 +71,8 @@ ignored-modules =
ipaplatform, ipaplatform.paths, ipaplatform.tasks, ipapython.admintool,
ipaserver.install.installutils, ipaserver.install.server.install,
ipaserver.install,
ipaclient.install.ipachangeconf, ipaclient.install.client
ipaclient.install.ipachangeconf, ipaclient.install.client,
ipaserver.dcerpc
[pylint.REFACTORING]
max-nested-blocks = 9

View File

@@ -63,6 +63,24 @@ IPA_SERVER_HOST=<ipaserver_host_or_ip> pytest -rs
For a complete list of options check `pytest --help`.
### Disabling and enabling playbook tests
Sometimes it is useful to enable or disable specific playbook tests. To only run a subset of modules or tests, use the variables IPA_ENABLED_MODULES and IPA ENABLED_TESTS, to define a comma-separated list of modules or tests to be enabled. Any test or module not in the list will not be executed. For example, to run only `sudorule` and `sudocmd` tests:
```
IPA_ENABLE_MODULES="sudorule,sudocmd" IPA_SERVER_HOST=<ipaserver_host_or_ip> pytest
```
If all but a few selected tests are to be executed, use the IPA_DISABLED_MODULES or IPA_DISABLED_TESTS. For example, to run all, but "test_service_certificate" test:
```
IPA_DISABLED_TESTS=test_service_certificate IPA_SERVER_HOST=<ipaserver_host_or_ip> pytest
```
If none of this variables are defined, all tests will be executed.
To configure the tests that will run for your pull request, add a TEMP commit, with the configuration defined in the file `tests/azure/templates/variables.yml`. Set the variables `ipa_enable_modules`, `ipa_enable_tests`, `ipa_disable_modules`, and `ipa_disable_tests`, in the same way as the equivalent environment variables.
### Types of tests
#### Playbook tests
@@ -92,19 +110,21 @@ pip install molecule[docker]>=3
Now you can start a test container using the following command:
```
molecule create -s centos-8
molecule create -s c8s
```
Note: Currently the containers available for running the tests are:
* fedora-latest
* centos-7
* centos-8
* c8s
* c9s
### Running the tests inside the container
To run the tests you will use pytest (works the same as for VMs).
```
RUN_TESTS_IN_DOCKER=1 IPA_SERVER_HOST=centos-8 pytest
RUN_TESTS_IN_DOCKER=1 IPA_SERVER_HOST=c8s pytest
```
### Cleaning up after tests
@@ -112,11 +132,12 @@ RUN_TESTS_IN_DOCKER=1 IPA_SERVER_HOST=centos-8 pytest
After running the tests you should probably destroy the test container using:
```
molecule destroy -s centos-8
molecule destroy -s c8s
```
See [Running the tests](#running-the-tests) section for more information on available options.
## Upcoming/desired improvements:
* A script to pre-config the complete test environment using virsh.

View File

@@ -9,55 +9,55 @@ stages:
# Fedora
- stage: FedoraLatest_Ansible_2_9
- stage: Fedora_Latest
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: fedora-latest
ansible_version: ">=2.9,<2.10"
ansible_version: "-core >=2.12,<2.13"
# Galaxy on Fedora
- stage: Galaxy_Fedora_Latest
dependsOn: []
jobs:
- template: templates/galaxy_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: fedora-latest
ansible_version: "-core >=2.12,<2.13"
# CentOS 9 Stream
- stage: c9s_Ansible_2_9
- stage: CentOS_9_Stream
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: c9s
ansible_version: ">=2.9,<2.10"
ansible_version: "-core >=2.12,<2.13"
# CentOS 8 Stream
- stage: c8s_Ansible_2_9
- stage: CentOS_8_Stream
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: c8s
ansible_version: ">=2.9,<2.10"
# # CentOS 8
#
# - stage: CentOS8_Ansible_2_9
# dependsOn: []
# jobs:
# - template: templates/group_tests.yml
# parameters:
# build_number: $(Build.BuildNumber)
# scenario: centos-8
# ansible_version: ">=2.9,<2.10"
ansible_version: "-core >=2.12,<2.13"
# CentOS 7
- stage: CentOS7_Ansible_2_9
- stage: CentOS_7
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: centos-7
ansible_version: ">=2.9,<2.10"
ansible_version: "-core >=2.12,<2.13"

View File

@@ -21,12 +21,6 @@ jobs:
container_name: centos-7
build_scenario_name: centos-7-build
# - template: templates/build_container.yml
# parameters:
# job_name_suffix: Centos8
# container_name: centos-8
# build_scenario_name: centos-8-build
- template: templates/build_container.yml
parameters:
job_name_suffix: C8S
@@ -44,3 +38,9 @@ jobs:
job_name_suffix: FedoraLatest
container_name: fedora-latest
build_scenario_name: fedora-latest-build
- template: templates/build_container.yml
parameters:
job_name_suffix: FedoraRawhide
container_name: fedora-rawhide
build_scenario_name: fedora-rawhide-build

View File

@@ -16,15 +16,6 @@ stages:
# Fedora
- stage: FedoraLatest_Ansible_2_9
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: fedora-latest
ansible_version: ">=2.9,<2.10"
- stage: FedoraLatest_Ansible_Core_2_11
dependsOn: []
jobs:
@@ -52,16 +43,92 @@ stages:
scenario: fedora-latest
ansible_version: ""
# CentoOS 9 Stream
- stage: c9s_Ansible_2_9
- stage: FedoraLatest_Ansible_Core_latest
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: c9s
ansible_version: ">=2.9,<2.10"
scenario: fedora-latest
ansible_version: "-core"
# Galaxy on Fedora
- stage: Galaxy_FedoraLatest_Ansible_Core_2_11
dependsOn: []
jobs:
- template: templates/galaxy_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: fedora-latest
ansible_version: "-core >=2.11,<2.12"
- stage: Galaxy_FedoraLatest_Ansible_Core_2_12
dependsOn: []
jobs:
- template: templates/galaxy_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: fedora-latest
ansible_version: "-core >=2.12,<2.13"
- stage: Galaxy_FedoraLatest_Ansible_latest
dependsOn: []
jobs:
- template: templates/galaxy_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: fedora-latest
ansible_version: ""
- stage: Galaxy_FedoraLatest_Ansible_Core_latest
dependsOn: []
jobs:
- template: templates/galaxy_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: fedora-latest
ansible_version: "-core"
# Fedora Rawhide
- stage: FedoraRawhide_Ansible_Core_2_11
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: fedora-rawhide
ansible_version: "-core >=2.11,<2.12"
- stage: FedoraRawhide_Ansible_Core_2_12
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: fedora-rawhide
ansible_version: "-core >=2.12,<2.13"
- stage: FedoraRawhide_Ansible_latest
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: fedora-rawhide
ansible_version: ""
- stage: FedoraRawhide_Ansible_Core_latest
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: fedora-rawhide
ansible_version: "-core"
# CentoOS 9 Stream
- stage: c9s_Ansible_Core_2_11
dependsOn: []
@@ -90,16 +157,16 @@ stages:
scenario: c9s
ansible_version: ""
# CentOS 8 Stream
- stage: c8s_Ansible_2_9
- stage: c9s_Ansible_Core_latest
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: c8s
ansible_version: ">=2.9,<2.10"
scenario: c9s
ansible_version: "-core"
# CentOS 8 Stream
- stage: c8s_Ansible_Core_2_11
dependsOn: []
@@ -128,54 +195,16 @@ stages:
scenario: c8s
ansible_version: ""
# # CentOS 8
#
# - stage: CentOS8_Ansible_2_9
# dependsOn: []
# jobs:
# - template: templates/group_tests.yml
# parameters:
# build_number: $(Build.BuildNumber)
# scenario: centos-8
# ansible_version: ">=2.9,<2.10"
#
# - stage: CentOS8_Ansible_Core_2_11
# dependsOn: []
# jobs:
# - template: templates/group_tests.yml
# parameters:
# build_number: $(Build.BuildNumber)
# scenario: centos-8
# ansible_version: "-core >=2.11,<2.12"
#
# - stage: CentOS8_Ansible_Core_2_12
# dependsOn: []
# jobs:
# - template: templates/group_tests.yml
# parameters:
# build_number: $(Build.BuildNumber)
# scenario: centos-8
# ansible_version: "-core >=2.12,<2.13"
#
# - stage: CentOS8_Ansible_latest
# dependsOn: []
# jobs:
# - template: templates/group_tests.yml
# parameters:
# build_number: $(Build.BuildNumber)
# scenario: centos-8
# ansible_version: ""
# CentOS 7
- stage: CentOS7_Ansible_2_9
- stage: c8s_Ansible_Core_latest
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: centos-7
ansible_version: ">=2.9,<2.10"
scenario: c8s
ansible_version: "-core"
# CentOS 7
- stage: CentOS7_Ansible_Core_2_11
dependsOn: []
@@ -203,3 +232,12 @@ stages:
build_number: $(Build.BuildNumber)
scenario: centos-7
ansible_version: ""
- stage: CentOS7_Ansible_Core_latest
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
scenario: centos-7
ansible_version: "-core"

View File

@@ -0,0 +1,58 @@
---
parameters:
- name: build_number
type: string
- name: scenario
type: string
default: fedora-latest
- name: ansible_version
type: string
default: ""
- name: python_version
type: string
default: 3.x
jobs:
- job: Test_PyTests
displayName: Run pytests on ${{ parameters.scenario }}
timeoutInMinutes: 120
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '${{ parameters.python_version }}'
- script: |
pip install \
"molecule[docker]>=3" \
"ansible${{ parameters.ansible_version }}"
displayName: Install molecule and Ansible
- script: ansible-galaxy collection install community.docker ansible.posix
displayName: Install Ansible collections
- script: pip install -r requirements-tests.txt
displayName: Install dependencies
- script: |
utils/build-galaxy-release.sh -i
molecule create -s ${{ parameters.scenario }}
displayName: Setup test container
env:
ANSIBLE_LIBRARY: ./molecule
- script: |
cd ~/.ansible/collections/ansible_collections/freeipa/ansible_freeipa
pytest \
-m "not playbook" \
--verbose \
--color=yes \
--junit-xml=TEST-results-pytests.xml
displayName: Run tests
env:
IPA_SERVER_HOST: ${{ parameters.scenario }}
RUN_TESTS_IN_DOCKER: true
- task: PublishTestResults@2
inputs:
mergeTestResults: true
testRunTitle: PlaybookTests-Build${{ parameters.build_number }}
condition: succeededOrFailed()

View File

@@ -0,0 +1,75 @@
---
parameters:
- name: group_number
type: number
default: 1
- name: number_of_groups
type: number
default: 1
- name: scenario
type: string
default: fedora-latest
- name: ansible_version
type: string
default: ""
- name: python_version
type: string
default: 3.x
- name: build_number
type: string
jobs:
- job: Test_Group${{ parameters.group_number }}
displayName: Run playbook tests ${{ parameters.scenario }} (${{ parameters.group_number }}/${{ parameters.number_of_groups }})
timeoutInMinutes: 120
variables:
- template: variables.yaml
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '${{ parameters.python_version }}'
- script: |
pip install \
"molecule[docker]>=3" \
"ansible${{ parameters.ansible_version }}"
displayName: Install molecule and Ansible
- script: ansible-galaxy collection install community.docker ansible.posix
displayName: Install Ansible collections
- script: pip install -r requirements-tests.txt
displayName: Install dependencies
- script: |
utils/build-galaxy-release.sh -i
molecule create -s ${{ parameters.scenario }}
displayName: Setup test container
env:
ANSIBLE_LIBRARY: ./molecule
- script: |
cd ~/.ansible/collections/ansible_collections/freeipa/ansible_freeipa
pytest \
-m "playbook" \
--verbose \
--color=yes \
--test-group-count=${{ parameters.number_of_groups }} \
--test-group=${{ parameters.group_number }} \
--test-group-random-seed=97943259814 \
--junit-xml=TEST-results-group-${{ parameters.group_number }}.xml
displayName: Run playbook tests
env:
IPA_SERVER_HOST: ${{ parameters.scenario }}
RUN_TESTS_IN_DOCKER: true
IPA_DISABLED_MODULES: ${{ variables.ipa_disabled_modules }}
IPA_DISABLED_TESTS: ${{ variables.ipa_disabled_tests }}
IPA_ENABLED_MODULES: ${{ variables.ipa_enabled_modules }}
IPA_ENABLED_TESTS: ${{ variables.ipa_enabled_tests }}
- task: PublishTestResults@2
inputs:
mergeTestResults: true
testRunTitle: PlaybookTests-Build${{ parameters.build_number }}
condition: succeededOrFailed()

View File

@@ -0,0 +1,41 @@
---
parameters:
- name: scenario
type: string
default: fedora-latest
- name: build_number
type: string
- name: ansible_version
type: string
default: ""
jobs:
- template: galaxy_script.yml
parameters:
group_number: 1
number_of_groups: 3
build_number: ${{ parameters.build_number }}
scenario: ${{ parameters.scenario }}
ansible_version: ${{ parameters.ansible_version }}
- template: galaxy_script.yml
parameters:
group_number: 2
number_of_groups: 3
build_number: ${{ parameters.build_number }}
scenario: ${{ parameters.scenario }}
ansible_version: ${{ parameters.ansible_version }}
- template: galaxy_script.yml
parameters:
group_number: 3
number_of_groups: 3
build_number: ${{ parameters.build_number }}
scenario: ${{ parameters.scenario }}
ansible_version: ${{ parameters.ansible_version }}
- template: galaxy_pytest_script.yml
parameters:
build_number: ${{ parameters.build_number }}
scenario: ${{ parameters.scenario }}
ansible_version: ${{ parameters.ansible_version }}

View File

@@ -18,11 +18,12 @@ parameters:
- name: build_number
type: string
jobs:
- job: Test_Group${{ parameters.group_number }}
displayName: Run playbook tests ${{ parameters.scenario }} (${{ parameters.group_number }}/${{ parameters.number_of_groups }})
timeoutInMinutes: 120
variables:
- template: variables.yaml
steps:
- task: UsePythonVersion@0
inputs:
@@ -63,6 +64,10 @@ jobs:
env:
IPA_SERVER_HOST: ${{ parameters.scenario }}
RUN_TESTS_IN_DOCKER: true
IPA_DISABLED_MODULES: ${{ variables.ipa_disabled_modules }}
IPA_DISABLED_TESTS: ${{ variables.ipa_disabled_tests }}
IPA_ENABLED_MODULES: ${{ variables.ipa_enabled_modules }}
IPA_ENABLED_TESTS: ${{ variables.ipa_enabled_tests }}
- task: PublishTestResults@2
inputs:

View File

@@ -16,6 +16,8 @@ jobs:
- job: Test_PyTests
displayName: Run pytests on ${{ parameters.scenario }}
timeoutInMinutes: 120
variables:
- template: variables.yaml
steps:
- task: UsePythonVersion@0
inputs:
@@ -53,6 +55,10 @@ jobs:
env:
IPA_SERVER_HOST: ${{ parameters.scenario }}
RUN_TESTS_IN_DOCKER: true
IPA_DISABLED_MODULES: ${{ variables.ipa_disabled_modules }}
IPA_DISABLED_TESTS: ${{ variables.ipa_disabled_tests }}
IPA_ENABLED_MODULES: ${{ variables.ipa_enabled_modules }}
IPA_ENABLED_TESTS: ${{ variables.ipa_enabled_tests }}
- task: PublishTestResults@2
inputs:

View File

@@ -0,0 +1,20 @@
#
# Variables must be defined as comma separated lists.
# For easier management of items to enable/disable,
# use one test/module on each line, followed by a comma.
#
# Example:
#
# disabled_modules: >-
# dnsconfig,
# group,
# hostgroup
#
---
variables:
# ipa_enabled_modules: >-
# ipa_enabled_tests: >-
ipa_disabled_modules: >-
dnsconfig,
ipa_disabled_tests: >-
test_dnsconfig_forwarders_ports

View File

@@ -13,14 +13,11 @@
forwarders:
- ip_address: 8.8.8.8
- ip_address: 8.8.4.4
- ip_address: 2001:4860:4860::8888
- ip_address: 2001:4860:4860::8888
port: 53
state: absent
action: member
# Tests.
- name: Set config to invalid IPv4.
- name: Set forward with invalid IPv4.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
@@ -54,8 +51,6 @@
forwarders:
- ip_address: 8.8.8.8
- ip_address: 8.8.4.4
- ip_address: 2001:4860:4860::8888
port: 53
forward_policy: only
allow_sync_ptr: yes
register: result
@@ -68,8 +63,6 @@
forwarders:
- ip_address: 8.8.8.8
- ip_address: 8.8.4.4
- ip_address: 2001:4860:4860::8888
port: 53
forward_policy: only
allow_sync_ptr: yes
register: result
@@ -97,14 +90,15 @@
register: result
failed_when: result.changed or result.failed
- name: Ensure forwarder 8.8.4.4 is present.
- name: Check if forwarder 8.8.4.4 is present.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 8.8.4.4
check_mode: yes
register: result
failed_when: not result.changed or result.failed
failed_when: result.changed or result.failed
- name: Ensure forwarder 8.8.8.8 is present.
ipadnsconfig:
@@ -115,35 +109,16 @@
register: result
failed_when: not result.changed or result.failed
- name: Ensure forwarder 8.8.4.4 is present.
- name: Check forwarder 8.8.4.4 is still present.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 8.8.4.4
check_mode: yes
register: result
failed_when: not result.changed or result.failed
- name: Ensure forwarders are absent.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 8.8.4.4
- ip_address: 8.8.8.8
register: result
failed_when: not result.changed or result.failed
- name: Ensure forwarders are absent, again.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 8.8.4.4
- ip_address: 8.8.8.8
register: result
failed_when: result.changed or result.failed
- name: Disable global forwarders.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
@@ -208,97 +183,6 @@
register: result
failed_when: result.changed or result.failed
- name: Ensure all forwarders are absent.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 8.8.8.8
- ip_address: 8.8.4.4
- ip_address: 2001:4860:4860::8888
port: 53
state: absent
action: member
register: result
failed_when: not result.changed or result.failed
- name: Ensure all forwarders are absent, again.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 8.8.8.8
- ip_address: 8.8.4.4
- ip_address: 2001:4860:4860::8888
port: 53
state: absent
action: member
register: result
failed_when: result.changed or result.failed
- name: Ensure forwarder is present.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 8.8.8.8
register: result
failed_when: not result.changed or result.failed
- name: Ensure forwarders is not present.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 8.8.4.4
check_mode: yes
register: result
failed_when: not result.changed or result.failed
- name: Ensure forwarders are present.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 8.8.4.4
- ip_address: 8.8.8.8
action: member
register: result
failed_when: not result.changed or result.failed
- name: Ensure forwarders are present, again.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 8.8.4.4
- ip_address: 8.8.8.8
action: member
register: result
failed_when: result.changed or result.failed
- name: Ensure another forwarder is present.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 8.8.4.4
action: member
register: result
failed_when: result.changed or result.failed
- name: Ensure forwarders are present.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 8.8.4.4
- ip_address: 8.8.8.8
action: member
register: result
failed_when: result.changed or result.failed
# Cleanup.
- name: Ensure forwarders are absent.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
@@ -306,8 +190,5 @@
forwarders:
- ip_address: 8.8.8.8
- ip_address: 8.8.4.4
- ip_address: 2001:4860:4860::8888
- ip_address: 2001:4860:4860::8888
port: 53
state: absent
action: member

View File

@@ -0,0 +1,81 @@
---
- name: Test dnsconfig forwarders with custom ports
hosts: "{{ ipa_test_host | default('ipaserver') }}"
become: true
gather_facts: no
tasks:
- block:
# Setup.
- name: Ensure forwarder with custom port is absent.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 2001:4860:4860::8888
port: 53
state: absent
action: member
# Tests.
- name: Ensure forwarder with custom port is present.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 2001:4860:4860::8888
port: 53
state: present
action: member
register: result
failed_when: not result.changed or result.failed
- name: Ensure forwarder with custom port is present, again.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 2001:4860:4860::8888
port: 53
state: present
action: member
register: result
failed_when: result.changed or result.failed
- name: Ensure forwarder with custom port is absent.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 2001:4860:4860::8888
port: 53
state: absent
action: member
register: result
failed_when: not result.changed or result.failed
- name: Ensure forwarder with custom port is absent, again.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 2001:4860:4860::8888
port: 53
state: absent
action: member
register: result
failed_when: result.changed or result.failed
always:
# Cleanup.
- name: Ensure forwarder with custom port is absent.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
forwarders:
- ip_address: 2001:4860:4860::8888
port: 53
state: absent
action: member

View File

@@ -119,7 +119,7 @@
name: local_id_range
- block:
# Create trust with range_type: ipa-ad-trust-posix
# Create trust with range_type: ipa-ad-trust
- name: Create trust with range_type 'ipa-ad-trust'
include_tasks: tasks_set_trust.yml
vars:
@@ -127,7 +127,7 @@
trust_range_size: 200000
trust_range_type: ipa-ad-trust
# Can't user secondary_rid_base with dom_sid/dom_name
# Can't use secondary_rid_base with dom_sid/dom_name
- name: Ensure AD-trust idrange is present
ipaidrange:
ipaadmin_password: SomeADMINpassword
@@ -227,6 +227,50 @@
name: ad_id_range
state: absent
# Create trust with range_type: ipa-ad-trust-posix
- name: Create trust with range_type 'ipa-ad-trust'
include_tasks: tasks_set_trust.yml
vars:
trust_base_id: 10000000
trust_range_size: 200000
trust_range_type: ipa-ad-trust
- name: Ensure AD-trust idrange is present, with dom_name
ipaidrange:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ad_id_range
base_id: 150000000
range_size: 200000
rid_base: 1000000
idrange_type: ipa-ad-trust
dom_name: "{{ adserver.domain }}"
auto_private_groups: "false"
register: result
failed_when: not result.changed or result.failed
# Remove trust and idrange
- name: Remove test trust.
include_tasks: tasks_remove_trust.yml
- name: Ensure AD-trust idrange is absent
ipaidrange:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ad_id_range
state: absent
# Remove trust and idrange
- name: Remove test trust.
include_tasks: tasks_remove_trust.yml
- name: Ensure AD-trust idrange is absent
ipaidrange:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ad_id_range
state: absent
# Create trust with range_type: ipa-ad-trust-posix
- name: Create trust with range_type 'ipa-ad-trust-posix'
include_tasks: tasks_set_trust.yml
@@ -235,7 +279,7 @@
trust_range_size: 2000000
trust_range_type: ipa-ad-trust-posix
# Can't user secondary_rid_base or rid_base with "ad-trust-posix"
# Can't use secondary_rid_base or rid_base with "ad-trust-posix"
- name: Ensure AD-trust-posix idrange is present
ipaidrange:
ipaadmin_password: SomeADMINpassword
@@ -260,6 +304,51 @@
register: result
failed_when: result.changed or result.failed
- name: Check if AD-trust-posix idrange is present, using dom_name
ipaidrange:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ad_posix_id_range
base_id: 150000000
range_size: 200000
idrange_type: ipa-ad-trust-posix
dom_name: "{{ adserver.domain }}"
check_mode: yes
register: result
failed_when: result.changed or result.failed
# Remove trust and idrange
- name: Remove test trust.
include_tasks: tasks_remove_trust.yml
- name: Ensure AD-trust idrange is absent
ipaidrange:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ad_posix_id_range
state: absent
# Create trust with range_type: ipa-ad-trust-posix
- name: Create trust with range_type 'ipa-ad-trust-posix'
include_tasks: tasks_set_trust.yml
vars:
trust_base_id: 10000000
trust_range_size: 2000000
trust_range_type: ipa-ad-trust-posix
# Can't use secondary_rid_base or rid_base with "ad-trust-posix"
- name: Ensure AD-trust-posix idrange is present, with dom_name
ipaidrange:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ad_posix_id_range
base_id: 150000000
range_size: 200000
idrange_type: ipa-ad-trust-posix
dom_name: "{{ adserver.domain }}"
register: result
failed_when: not result.changed or result.failed
always:
# CLEANUP TEST ITEMS
- name: Remove test trust.

View File

@@ -64,18 +64,26 @@ class TestDNSZone(AnsibleFreeIPATestCase):
def test_dnszone_disable(self):
"""TC-30: Disable DNS Zone."""
zone26 = "26testzone.test"
self.check_details(["Active zone: TRUE"], "dnszone-find", [zone26])
self.check_details(
["Active zone: (TRUE|True)"], "dnszone-find", [zone26]
)
# Disable dns zone
self.run_playbook(BASE_PATH + "dnszone_disable.yaml")
self.check_details(["Active zone: FALSE"], "dnszone-find", [zone26])
self.check_details(
["Active zone: (FALSE|False)"], "dnszone-find", [zone26]
)
def test_dnszone_enable(self):
"""TC-31: Enable DNS Zone."""
zone26 = "26testzone.test"
self.check_details(["Active zone: FALSE"], "dnszone-find", [zone26])
self.check_details(
["Active zone: (FALSE|False)"], "dnszone-find", [zone26]
)
# Enable dns zone
self.run_playbook(BASE_PATH + "dnszone_enable.yaml")
self.check_details(["Active zone: TRUE"], "dnszone-find", [zone26])
self.check_details(
["Active zone: (TRUE|True)"], "dnszone-find", [zone26]
)
def test_dnszone_name_from_ip(self):
"""TC-35: Add dns zone with reverse zone IP. Bug#1845056."""

View File

@@ -43,7 +43,6 @@ tests/sanity/sanity.sh shebang!skip
tests/user/users.sh shebang!skip
tests/user/users_absent.sh shebang!skip
tests/utils.py pylint:ansible-format-automatic-specification
tests/utils.py pylint:subprocess-run-check
utils/ansible-doc-test shebang!skip
utils/ansible-ipa-client-install shebang!skip
utils/ansible-ipa-replica-install shebang!skip

View File

@@ -1,8 +1,13 @@
#!/bin/bash
TOPDIR=$(readlink -f "$(dirname "$0")/../..")
pushd "${TOPDIR}" >/dev/null || exit 1
VENV=/tmp/ansible-test-venv
ANSIBLE_COLLECTION=freeipa-ansible_freeipa
use_docker=$(which docker >/dev/null 2>&1 && echo "True" || echo "False")
virtualenv "$VENV"
# shellcheck disable=SC1091
source "$VENV"/bin/activate
@@ -15,7 +20,8 @@ rm -f importer_result.json
utils/build-galaxy-release.sh
export GALAXY_IMPORTER_CONFIG=tests/sanity/galaxy-importer.cfg
sed "s/LOCAL_IMAGE_DOCKER = True/LOCAL_IMAGE_DOCKER = ${use_docker}/" < tests/sanity/galaxy-importer.cfg > ${VENV}/galaxy-importer.cfg
export GALAXY_IMPORTER_CONFIG=${VENV}/galaxy-importer.cfg
collection=$(ls -1 "$ANSIBLE_COLLECTION"-*.tar.gz)
echo "Running: python -m galaxy_importer.main $collection"
@@ -33,4 +39,6 @@ done < <(python -m galaxy_importer.main "$collection")
rm -rf "$VENV"
popd >/dev/null || exit 1
exit "$error"

View File

@@ -10,7 +10,7 @@
- block:
- name: Get server name from hostname
set_fact:
ipa_server_name: "{{ ansible_facts['hostname'].split('.')[0] }}"
ipa_server_name: "{{ ansible_facts['fqdn'].split('.')[0] }}"
rescue:
- name: Fallback to 'ipaserver'
set_fact:
@@ -20,7 +20,7 @@
- block:
- name: Get domain name from hostname.
set_fact:
ipaserver_domain: "{{ ansible_facts['hostname'].split('.')[0][1:] }}"
ipaserver_domain: "{{ ansible_facts['fqdn'].split('.')[1:] | join('.') }}"
rescue:
- name: Fallback to 'ipa.test'
set_fact:

View File

@@ -24,11 +24,12 @@ import functools
from unittest import TestCase
from utils import get_test_playbooks, get_server_host, run_playbook
from utils import get_test_playbooks, get_skip_conditions, run_playbook
def prepare_test(test_name, test_path):
"""Decorator for the tests generated automatically from playbooks.
def prepare_test(testname, testpath):
"""
Decorate tests generated automatically from playbooks.
Injects 2 arguments to the test (`test_path` and `test_name`) and
name the test method using test name (to ensure test reports are useful).
@@ -36,13 +37,13 @@ def prepare_test(test_name, test_path):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
kwargs["test_path"] = test_path
kwargs["test_name"] = test_name
kwargs["test_path"] = testpath
kwargs["test_name"] = testname
return func(*args, **kwargs)
return wrapper
decorator.__name__ = test_name
decorator.__name__ = testname
return decorator
@@ -50,18 +51,21 @@ def prepare_test(test_name, test_path):
# test_* methods.
for test_dir_name, playbooks_in_dir in get_test_playbooks().items():
_tests = {}
for playbook in playbooks_in_dir:
test_name = playbook["name"].replace("-", "_")
test_path = playbook["path"]
@pytest.mark.skipif(
not get_server_host(),
reason="Environment variable IPA_SERVER_HOST must be set",
)
skip = get_skip_conditions(test_dir_name, test_name) or {}
# pylint: disable=W0621,W0640,W0613
@pytest.mark.skipif(**skip)
@pytest.mark.playbook
@prepare_test(test_name, test_path)
def method(self, test_path, test_name):
run_playbook(test_path)
# pylint: enable=W0621,W0640,W0613
_tests[test_name] = method
globals()[test_dir_name] = type(test_dir_name, tuple([TestCase]), _tests,)

View File

@@ -21,6 +21,7 @@
import os
import pytest
import re
import subprocess
import tempfile
import testinfra
@@ -45,6 +46,68 @@ def get_server_host():
return os.getenv("IPA_SERVER_HOST")
def get_disabled_test(group_name, test_name):
disabled_modules = [
disabled.strip()
for disabled in os.environ.get("IPA_DISABLED_MODULES", "").split(",")
]
disabled_tests = [
disabled.strip()
for disabled in os.environ.get("IPA_DISABLED_TESTS", "").split(",")
if disabled.strip()
]
if not any([disabled_modules, disabled_tests]):
return False
return group_name in disabled_modules or test_name in disabled_tests
def get_enabled_test(group_name, test_name):
enabled_modules = [
enabled.strip()
for enabled in os.environ.get("IPA_ENABLED_MODULES", "").split(":")
if enabled.strip()
]
enabled_tests = [
enabled.strip()
for enabled in os.environ.get("IPA_ENABLED_TESTS", "").split(":")
if enabled.strip()
]
if not any([enabled_modules, enabled_tests]):
return True
group_enabled = group_name in enabled_modules
test_enabled = test_name in enabled_tests
return group_enabled or test_enabled
def get_skip_conditions(group_name, test_name):
"""
Check tests that need to be skipped.
The return is a dict containing `condition` and `reason`. For the test
to be skipped, `condition` must be True, if it is `False`, the test is
to be skipped. Although "reason" must be always provided, it can be
`None` if `condition` is True.
"""
if not get_server_host():
return {
"condition": True,
"reason": "Environment variable IPA_SERVER_HOST must be set",
}
if not get_enabled_test(group_name, test_name):
return {"condition": True, "reason": "Test not configured to run"}
if get_disabled_test(group_name, test_name):
return {"condition": True, "reason": "Test configured to not run"}
return {"condition": False, "reason": "Test will run."}
def get_inventory_content():
"""Create the content of an inventory file for a test run."""
ipa_server_host = get_server_host()
@@ -112,6 +175,7 @@ def _run_playbook(playbook):
inventory_file.name,
playbook,
]
# pylint: disable=subprocess-run-check
process = subprocess.run(
cmd, cwd=SCRIPT_DIR, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
@@ -238,17 +302,23 @@ class AnsibleFreeIPATestCase(TestCase):
host_connection_info, ssh_identity_file=ssh_identity_file,
)
def run_playbook(self, playbook, allow_failures=False):
@staticmethod
def run_playbook(playbook, allow_failures=False):
return run_playbook(playbook, allow_failures)
def run_playbook_with_exp_msg(self, playbook, expected_msg):
result = self.run_playbook(playbook, allow_failures=True)
@staticmethod
def run_playbook_with_exp_msg(playbook, expected_msg):
result = run_playbook(playbook, allow_failures=True)
assert (
expected_msg in result.stdout.decode("utf8")
or
expected_msg in result.stderr.decode("utf8")
)
@staticmethod
def __is_text_on_data(text, data):
return re.search(text, data) is not None
def check_details(self, expected_output, cmd, extra_cmds=None):
cmd = "ipa " + cmd
if extra_cmds:
@@ -257,10 +327,16 @@ class AnsibleFreeIPATestCase(TestCase):
res = self.master.run(cmd)
if res.rc != 0:
for output in expected_output:
assert output in res.stderr
assert self.__is_text_on_data(output, res.stderr), (
f"\n{'='*40}\nExpected: {output}\n{'='*40}\n"
+ f"Output:\n{res.stderr}{'='*40}\n"
)
else:
for output in expected_output:
assert output in res.stdout
assert self.__is_text_on_data(output, res.stdout), (
f"\n{'='*40}\nExpected: {output}\n{'='*40}\n"
+ f"Output:\n{res.stdout}{'='*40}\n"
)
kdestroy(self.master)
def check_notexists(self, members, cmd, extra_cmds=None):
@@ -270,7 +346,10 @@ class AnsibleFreeIPATestCase(TestCase):
kinit_admin(self.master)
res = self.master.run(cmd)
for member in members:
assert member not in res.stdout
assert not self.__is_text_on_data(member, res.stdout), (
f"\n{'='*40}\nExpected: {member}\n{'='*40}\n"
+ f"Output:\n{res.stdout}{'='*40}\n"
)
kdestroy(self.master)
def mark_xfail_using_ansible_freeipa_version(self, version, reason):

View File

@@ -19,6 +19,7 @@ be givedn without the other one.
Options:
-a Add all files, no only files known to git repo
-k Keep build directory
-i Install the generated collection
-h Print this help
EOF
@@ -26,7 +27,8 @@ EOF
all=0
keep=0
while getopts "ahk" arg; do
install=0
while getopts "ahki" arg; do
case $arg in
a)
all=1
@@ -38,6 +40,9 @@ while getopts "ahk" arg; do
k)
keep=1
;;
i)
install=1
;;
\?)
echo
usage
@@ -70,7 +75,7 @@ if [ -z "$galaxy_version" ]; then
exit 1
fi
echo "Builing galaxy release: ${namespace}-${collection}-${galaxy_version}"
echo "Building collection: ${namespace}-${collection}-${galaxy_version}"
GALAXY_BUILD=".galaxy-build"
@@ -102,6 +107,11 @@ sed -i -e "s/name: .*/name: \"$collection\"/" galaxy.yml
find . -name "*~" -exec rm {} \;
echo "Creating CHANGELOG.rst..."
"$(dirname "$0")/changelog" --galaxy > CHANGELOG.rst
echo -e "\033[ACreating CHANGELOG.rst... \033[32;1mDONE\033[0m"
sed -i -e "s/ansible.module_utils.ansible_freeipa_module/ansible_collections.${collection_prefix}.plugins.module_utils.ansible_freeipa_module/" plugins/modules/*.py
(cd plugins/module_utils && {
@@ -182,3 +192,8 @@ if [ $keep == 0 ]; then
else
echo "Keeping build dir $GALAXY_BUILD"
fi
if [ $install == 1 ]; then
echo "Installing collection ${namespace}-${collection}-${galaxy_version}.tar.gz ..."
ansible-galaxy collection install "${namespace}-${collection}-${galaxy_version}.tar.gz" --force
fi

View File

@@ -25,48 +25,6 @@ import argparse
import subprocess
usage = "Usage: changelog [options] [<new version>]"
parser = argparse.ArgumentParser(usage=usage)
parser.add_argument("--tag", dest="tag",
help="git tag")
options, args = parser.parse_known_args()
if len(args) == 1:
new_version = args[0]
elif len(args) != 0:
parser.error("new version is not set")
else:
new_version = None
if options.tag is None:
tag = subprocess.check_output(
"git describe --tags $(git rev-list --tags --max-count=1)",
shell=True)
options.tag = tag.decode("utf-8").strip()
version = options.tag[1:]
command = ["git", "log", "%s.." % options.tag]
process = subprocess.run(command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
if process.returncode != 0:
print("git log failed: %s" % process.stderr.decode("utf8").split("\n")[0])
sys.exit(1)
if new_version is not None:
s = "ansible-freeipa-%s" % new_version
print(s)
print("=" * len(s))
print()
commits = {}
prs = {}
authors = {}
lines = process.stdout.decode("utf-8").split("\n")
class Ref:
def __init__(self, commit):
self.commit = commit
@@ -75,11 +33,11 @@ class Ref:
def store(commits, prs, authors, commit, author, merge, msg):
if commit is not None:
if msg[0].startswith("Merge pull request #"):
pr = int(msg[0].split()[3][1:])
pr_ref = int(msg[0].split()[3][1:])
if len(msg) > 1:
prs[pr] = msg[1].strip()
prs[pr_ref] = msg[1].strip()
else:
prs[pr] = Ref(merge)
prs[pr_ref] = Ref(merge)
else:
commits[commit] = msg[0].strip()
authors.setdefault(author, []).append(commit)
@@ -93,57 +51,125 @@ def get_commit(commits, commit):
return commit
commit = None
author = None
merge = None
msg = None
for line in lines:
line = line.rstrip()
if line.startswith("commit "):
def get_output(command):
try:
ret = subprocess.check_output(command, shell=True)
ret = ret.decode("utf-8").strip()
except subprocess.CalledProcessError:
print("Command '%s' failed" % command)
sys.exit(1)
return ret
def changelog(tag):
prev_tag = None
if tag is not None and tag != "":
prev_tag = get_output(
"git describe --tag --abbrev=0 --always '%s^'" % tag)
else:
tag = get_output("git describe --tags --abbrev=0 "
"$(git rev-list --tags --max-count=1)")
version = tag[1:]
if prev_tag is not None:
command = ["git", "log", "%s..%s" % (prev_tag, tag)]
else:
command = ["git", "log", "%s.." % tag]
process = subprocess.run(command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
if process.returncode != 0:
print("git log failed: %s" %
process.stderr.decode("utf8").split("\n")[0])
sys.exit(1)
lines = process.stdout.decode("utf-8").split("\n")
commits = {}
prs = {}
authors = {}
commit = None
author = None
merge = None
msg = None
for line in lines:
line = line.rstrip()
if line.startswith("commit "):
store(commits, prs, authors, commit, author, merge, msg)
author = None
msg = []
commit = line[7:]
elif line.startswith(" "):
msg.append(line[4:])
else:
try:
key, value = line.split(":", 1)
if key == "Author":
author = value.split("<")[0].strip()
elif key == "Merge":
merge = value.split()[1].strip()
# Ignore Date, ..
except ValueError:
pass
# Add final commit
if commit:
store(commits, prs, authors, commit, author, merge, msg)
author = None
msg = []
commit = line[7:]
elif line.startswith(" "):
msg.append(line[4:])
if prev_tag is not None:
line = "Changes for %s since %s" % (version, prev_tag[1:])
else:
try:
key, value = line.split(":", 1)
if key == "Author":
author = value.split("<")[0].strip()
elif key == "Merge":
merge = value.split()[1].strip()
# Ignore Date, ..
except ValueError:
pass
# Add final commit
if commit:
store(commits, prs, authors, commit, author, merge, msg)
s = "Changes since %s" % version
print("%s" % s)
print("-" * len(s))
print()
prs_sorted = sorted(prs.keys(), reverse=True)
for pr in prs_sorted:
if isinstance(prs[pr], Ref):
msg = get_commit(commits, prs[pr].commit)
else:
msg = prs[pr]
print(" - %s (#%d)" % (msg, pr))
print()
s = "Detailed changelog since %s by author" % version
print("%s" % s)
print("-" * len(s))
print(" %d authors, %d commits" % (len(authors), len(commits)))
print()
authors_sorted = sorted(authors.keys())
for author in authors_sorted:
print("%s (%d)\n" % (author, len(authors[author])))
for commit in authors[author]:
print(" - %s" % commits[commit])
line = "Changes since %s" % version
print("%s" % line)
print("-" * len(line))
print()
prs_sorted = sorted(prs.keys(), reverse=True)
for pr_ref in prs_sorted:
if isinstance(prs[pr_ref], Ref):
msg = get_commit(commits, prs[pr_ref].commit)
else:
msg = prs[pr_ref]
print(" - %s (#%d)" % (msg, pr_ref))
print()
if prev_tag is not None:
line = "Detailed changelog for %s since %s by author" % (version,
prev_tag[1:])
else:
line = "Detailed changelog since %s by author" % version
print("%s" % line)
print("-" * len(line))
print(" %d authors, %d commits" % (len(authors), len(commits)))
print()
authors_sorted = sorted(authors.keys())
for author in authors_sorted:
print("%s (%d)\n" % (author, len(authors[author])))
for commit in authors[author]:
print(" - %s" % commits[commit])
print()
parser = argparse.ArgumentParser(usage="Usage: changelog [options]")
parser.add_argument("--tag", dest="tag", help="git tag")
parser.add_argument("--galaxy", dest="galaxy", action="store_true",
help="Create changelog for galaxy")
options, args = parser.parse_known_args()
if len(args) != 0:
parser.print_help()
sys.exit(1)
if options.galaxy:
# Get latest tag
tag = get_output("git describe --tag --abbrev=0")
# get number of commits since latest tag
count = get_output("git rev-list '%s'.. --count" % tag)
if count != "0":
changelog(None)
changelog(tag)
else:
changelog(options.tag)