Compare commits

...

100 Commits

Author SHA1 Message Date
Varun Mylaraiah
a6a95e7649 Merge pull request #302 from t-woerner/caless_server_fix
ipaserver/library/ipaserver_setup_ca.py: Fix bug introduced with ca-less PR
2020-06-15 14:18:19 +05:30
Thomas Woerner
6b2b9ea787 ipaserver/library/ipaserver_setup_ca.py: Fix bug introduced with ca-less PR
The ca-less PR introduced a bug when http_ca_cert is not set. The test
for loading the certificate is testing for None, but the string will only
be empty in this case.

Related: #298 (Install server and replicas without CA)
2020-06-15 09:48:28 +02:00
Thomas Woerner
3487efcf9f galaxy.yml: Remove license_file
Galaxy refuses to import a collection that has license and license_file set
in galaxy.yml. Therefore license_file has been removed.
2020-06-11 19:33:37 +02:00
Thomas Woerner
695ad6307d Merge pull request #287 from rjeffman/fix_hbac_sudo_rule_hostcategory
Fixes attempt to create rules with members when category is `all`.
2020-06-11 16:55:28 +02:00
Rafael Guterres Jeffman
cf54d139c2 Fixes attempt to create rules with members when category is all.
Current implementation of hbacrule and sudorule allow for a new rule
creation script to be partialy successful when a member is provided and
the respective member category is set to `all` (either users, hosts,
services, commands, and their group counterparts).

Since the creation of the rule is independent of the adittion of members,
the rule is succesfully created, but member addition fails, leaving with
a created rule that has no members on it.

This patch fixes both modules by verifying if user, host, service or
commands (and groups of members) are being added if the corresponding
category is set to `all`, when the state is `present` and the action is
not `member`. If so, it fails before the rule is created.
2020-06-11 11:48:00 -03:00
Rafael Guterres Jeffman
ae471de0bd Merge pull request #283 from seocam/fix-test-entry-point
Fix all tests entry point
2020-06-11 11:47:47 -03:00
Rafael Guterres Jeffman
927329326c Reformatted README for better presentation on 80 column terminals. 2020-06-11 11:19:25 -03:00
Rafael Guterres Jeffman
26444b42b0 Merge pull request #298 from samuelvl/fix_ipareplicas_ca_less
Install server and replicas without CA
2020-06-11 11:13:23 -03:00
Thomas Woerner
1d196bca67 Merge pull request #296 from rjeffman/fix_dnsconfig_error_message
Fixes error handling on dnsconfig module.
2020-06-11 16:07:44 +02:00
Rafael Guterres Jeffman
d73b6e3920 Fixes error handling on dnsconfig module.
This fixes reporting errors on dnsconfig module and add some tests
to verify that invalid IP addresses cannot be used as forwarders.
2020-06-11 11:02:12 -03:00
Thomas Woerner
b80d6b061d Merge pull request #182 from chr15p/config
add an ipaconfig module
2020-06-11 15:36:09 +02:00
Thomas Woerner
5a290565f3 Merge pull request #235 from rjeffman/dnsrecord
New dnsrecord management module.
2020-06-11 15:27:39 +02:00
Thomas Woerner
40048c781a Merge pull request #275 from rjeffman/vault_add_state_retrieved
Vault add state retrieved
2020-06-11 15:06:26 +02:00
Rafael Guterres Jeffman
f7ca62e52b Add support for missing attributes, and enhance ipaconfig tests.
This patch add support for the attributes `maxtostname` and
`ca_renewal_master_server` attributes that were missing and
also provide a more complete set of tests.
2020-06-11 09:23:50 -03:00
Rafael Guterres Jeffman
da87f1648e Split vault tests in different files.
This change split vault tests in several files, organized by vault
type and operation (vault vs. member) so that it is easier to add
new tests for issues and verify if tests are missing.
2020-06-11 09:10:08 -03:00
Rafael Guterres Jeffman
0bcb4eaf0f Add state retrieved to ipavault to retrieve vault stored data.
This patch adds support for retrieving data stored in an IPA vault by
adding a new valid state for ipavault: `retrieved`.

To allow the retrieval of data from assymetric vaults, the attributes
`private_key`, `private_key_files` and `out` were also added to the
module.

The private key files, `private.pem`, should be paired with the already
existing `public.pem` public key files.

Tests were updated to reflect changes and two new playbooks were added:

    playbooks/vault/retrive-data-asymmetric-vault.yml
    playbooks/vault/retrive-data-symmetric-vault.yml
2020-06-11 09:10:08 -03:00
Rafael Guterres Jeffman
0456424821 Fixes password behavior on Vault module.
This patch fixes handling of password and public_key files, parameter
validation depending on vault type, usage of `salt` attribute and data
retrieval.

Tests were updated to reflect the changes.

New example playbooks are added:

    playbooks/vault/vault-is-present-with-password-file.yml
    playbooks/vault/vault-is-present-with-public-key-file.yml
2020-06-11 09:10:08 -03:00
Thomas Woerner
ff03b3153b ipahostgroup: Add support for group membership management
A group membership manager is a user or a group that can add members to
a group or remove members from a hostgroup.

This is related to https://pagure.io/freeipa/issue/8114

New parameters have been added to the module:
- `membermanager_user`: List of member manager users assigned to this
  group. Only usable with IPA versions 4.8.4 and up.
- `membermanager_group`: List of member manager groups assigned to this
  group. Only usable with IPA versions 4.8.4 and up.

These parameters behave like member parameters.

A new test has been added:
- tests/hostgroup/test_hostgroup_membermanager.yml
2020-06-11 09:10:08 -03:00
Rafael Guterres Jeffman
0abfe8ab90 New dnsrecord management module.
There is a new dnsrecord managem module placed in the plugins folder:

    plugins/modules/ipadnsrecord.py

The dnsrecord module allows management of DNS records and is as compatible
as possible with the Ansible upstream `ipa_dnsrecord` module, but provide
some other features like multiple record management in one execution,
support for more DNS record types, and more.

Here is the documentation for the module:

    README-dnsrecord

New example playbooks have been added:

    playbooks/dnsrecord/ensure-dnsrecord-is-absent.yml
    playbooks/dnsrecord/ensure-dnsrecord-is-present.yml
    playbooks/dnsrecord/ensure-presence-multiple-records.yml
    playbooks/dnsrecord/ensure-dnsrecord-with-reverse-is-present.yml
    playbooks/dnsrecord/ensure-multiple-A-records-are-present.yml
    playbooks/dnsrecord/ensure-A-and-AAAA-records-are-absent.yml
    playbooks/dnsrecord/ensure-A-and-AAAA-records-are-present.yml
    playbooks/dnsrecord/ensure-CNAME-record-is-absent.yml
    playbooks/dnsrecord/ensure-CNAME-record-is-present.yml
    playbooks/dnsrecord/ensure-MX-record-is-present.yml
    playbooks/dnsrecord/ensure-PTR-record-is-present.yml
    playbooks/dnsrecord/ensure-SRV-record-is-present.yml
    playbooks/dnsrecord/ensure-SSHFP-record-is-present.yml
    playbooks/dnsrecord/ensure-TLSA-record-is-present.yml
    playbooks/dnsrecord/ensure-TXT-record-is-present.yml
    playbooks/dnsrecord/ensure-URI-record-is-present.yml

New tests for the module can be found at:

    tests/dnsrecord/test_dnsrecord.yml
    tests/dnsrecord/test_compatibility_with_ansible_module.yml
    tests/dnsrecord/test_dnsrecord_full_records.yml
2020-06-11 09:02:31 -03:00
Thomas Woerner
89ba344a0b tests/config/test_config.yml: Fix main name
It should be `Playbook to handle server configuration` instead of
`Playbook to handle users`.
2020-06-10 11:59:22 +02:00
Samuel Veloso
c49fa4e899 Fix KDC certificate permissions 2020-06-09 14:48:07 +02:00
Samuel Veloso
66936d1afa Test ipaserver installation without CA 2020-06-09 14:33:03 +02:00
Samuel Veloso
c26b9c27b1 Include ipaserver changes 2020-06-09 14:31:53 +02:00
Samuel Veloso
ad139256df Test ipareplicas installation without CA 2020-06-09 14:25:34 +02:00
Samuel Veloso
d3b0fcebda Remove temporary certificates after installation is completed 2020-06-09 13:26:30 +02:00
Samuel Veloso
19b117a71c Install iparelicas without CA 2020-06-09 13:22:12 +02:00
Rafael Guterres Jeffman
02705c9e47 Merge pull request #295 from t-woerner/ipahostgroup_membermanager
ipahostgroup: Add support for group membership management
2020-06-09 08:18:08 -03:00
Rafael Guterres Jeffman
10e7b4094d Merge pull request #294 from t-woerner/ipagroup_membermanager
ipagroup: Add support for group membership management
2020-06-09 08:15:48 -03:00
Thomas Woerner
0acf576d99 ipagroup: Add support for group membership management
A group membership manager is a user or a group that can add members to
a group or remove members from a group.

This is related to https://pagure.io/freeipa/issue/8114

New parameters have been added to the module:
- `membermanager_user`: List of member manager users assigned to this
  group. Only usable with IPA versions 4.8.4 and up.
- `membermanager_group`: List of member manager groups assigned to this
  group. Only usable with IPA versions 4.8.4 and up.

These parameters behave like member parameters.

A new test has been added:
- tests/group/test_group_membermanager.yml
2020-06-09 11:03:47 +02:00
Thomas Woerner
fd7eb4f85f ipahostgroup: Add support for group membership management
A group membership manager is a user or a group that can add members to
a group or remove members from a hostgroup.

This is related to https://pagure.io/freeipa/issue/8114

New parameters have been added to the module:
- `membermanager_user`: List of member manager users assigned to this
  group. Only usable with IPA versions 4.8.4 and up.
- `membermanager_group`: List of member manager groups assigned to this
  group. Only usable with IPA versions 4.8.4 and up.

These parameters behave like member parameters.

A new test has been added:
- tests/hostgroup/test_hostgroup_membermanager.yml
2020-06-09 11:02:08 +02:00
Rafael Guterres Jeffman
2e7df27fe3 Add support for service-add-smb.
This patch adds variable `smb`, that can be used when adding a new
service, and creates a SMB service (cifs) with an optional
`netbiosname`.
2020-06-07 19:22:12 -03:00
Rafael Guterres Jeffman
561cd4fb98 Add support for FreeIPA API service_del continue option. 2020-06-07 19:22:12 -03:00
Rafael Guterres Jeffman
4ad1033685 Removed invalid state enabled from available choices. 2020-06-07 19:22:12 -03:00
Rafael Guterres Jeffman
3981dafd7b Allow clearing auth_ind by using "" as input value. 2020-06-07 19:22:12 -03:00
Rafael Guterres Jeffman
1cf251baf8 Fix error message when adding a service without principal. 2020-06-07 19:22:12 -03:00
Rafael Guterres Jeffman
c9210ca2d1 Allow the use of multiple values with auth_ind variable.
This patch changes auth_ind variable to receive a list of values
instead of a single one, so that more than one value can be set
at once.

Tests have been updated to reflect the change.
2020-06-07 19:22:12 -03:00
Rafael Guterres Jeffman
d7a3b7533c Fixes message when variable cannot be used in a given state action.
When using a variable that is invalid for a given action, the `action`
was not being displayed in the error message, leading to a poor user
experience.
2020-06-07 19:22:12 -03:00
Sergio Oliveira
46caacd0ae Merge pull request #290 from rjeffman/fix_service_module
Fix service module
2020-06-05 20:15:13 -03:00
Rafael Guterres Jeffman
5406c60157 Add support for service-add-smb.
This patch adds variable `smb`, that can be used when adding a new
service, and creates a SMB service (cifs) with an optional
`netbiosname`.
2020-06-05 19:33:38 -03:00
Rafael Guterres Jeffman
341078ed5d Add support for FreeIPA API service_del continue option. 2020-06-05 19:33:38 -03:00
Rafael Guterres Jeffman
95d90ef31f Removed invalid state enabled from available choices. 2020-06-05 19:33:38 -03:00
Rafael Guterres Jeffman
cf0b710047 Allow clearing auth_ind by using "" as input value. 2020-06-05 19:33:38 -03:00
Rafael Guterres Jeffman
bf9024f79f Fix error message when adding a service without principal. 2020-06-05 19:33:38 -03:00
Rafael Guterres Jeffman
f44e33c6b3 Allow the use of multiple values with auth_ind variable.
This patch changes auth_ind variable to receive a list of values
instead of a single one, so that more than one value can be set
at once.

Tests have been updated to reflect the change.
2020-06-05 19:33:38 -03:00
Rafael Guterres Jeffman
6b5f034912 Fixes message when variable cannot be used in a given state action.
When using a variable that is invalid for a given action, the `action`
was not being displayed in the error message, leading to a poor user
experience.
2020-06-05 19:33:38 -03:00
Rafael Guterres Jeffman
bf0b1ed75f Fixes no_log warning for update_password.
This patch explicitly set `no_log` option for `update_password` attribute
to `False`, so that the warning on `no_log` not being set is not issued
anymore. Ansible incorrectly issued the warning, as `update_password` does
not carry sensitive information.
2020-06-05 19:33:38 -03:00
Sergio Oliveira
a052160cc9 Merge pull request #286 from rjeffman/fix_user_update_password_warning
Fixes no_log warning for `update_password`.
2020-06-05 16:23:49 -03:00
Sergio Oliveira
851c6a9f39 Merge pull request #263 from rjeffman/fix_vault_password_handling
Fixes password behavior on Vault module.
2020-06-05 16:16:49 -03:00
Rafael Guterres Jeffman
59cb7eebd9 Fixes password behavior on Vault module.
This patch fixes handling of password and public_key files, parameter
validation depending on vault type, usage of `salt` attribute and data
retrieval.

Tests were updated to reflect the changes.

New example playbooks are added:

    playbooks/vault/vault-is-present-with-password-file.yml
    playbooks/vault/vault-is-present-with-public-key-file.yml
    playbooks/vault/retrive-data-asymmetric-vault.yml
    playbooks/vault/retrive-data-symmetric-vault.yml
2020-06-05 15:16:51 -03:00
Thomas Woerner
55e86c924f Merge pull request #289 from rjeffman/fix_host_absent_no_dns_zone
Fixes host absent when DNS zone is not found.
2020-06-05 17:27:16 +02:00
chrisp
56b1368441 There is a new config management module placed in the plugins folder:
plugins/modules/ipaconfig.py

The config module allows the user change global config settings.

The config module is as compatible as possible to the Ansible upstream
ipa_config module, but adds many extra variables.

Here is the documentation for the module:

  README-config.md
2020-06-05 14:58:46 +01:00
Thomas Woerner
4ada6e1d24 Merge pull request #264 from rjeffman/fix_vault_services
Add missing attribute `services` to vault module.
2020-06-05 15:58:16 +02:00
Rafael Guterres Jeffman
b48b81a030 Merge pull request #272 from ivarmu/master
Wrong variable names in the documentation
2020-06-04 10:50:38 -03:00
Thomas Woerner
09fefbb2d4 library/ipaserver_setup_ca: Use x509 IPA upstream code for pkcs12 files
With the encoded _http_ca_cert from ipaserver_test it is possible to revert
back to the IPA upstream code to write the pkcs12 http certificates.

The passed _http_ca_cert only needs to be decoded with decode_certificate.
2020-06-03 12:53:34 +02:00
Thomas Woerner
8e6d433df8 ipaserver/tasks/install.yml: Always remove temporary pkcs12 copies
The created temporary pkcs12 copies need to be removed in all cases. A
new task has been added.
2020-06-03 12:53:34 +02:00
Thomas Woerner
578d08c796 library/ipaserver_test: Revert to IPA upstream code for pkcs12 files
The function load_pkcs12 should not be skipped to verify the given
certificates. After the certificates have been verified and the temporary
certificate copies have been generated, these files are copied to
/etc/ipa/.tmp_pkcs12_* as the temporary files will simply be removed as
soon as the file descriptors have been closed.

Additionally the [http,dirsrv,pkinit]_pkcs12_info is recreated to point to
the copied temporary files.

With this revertion the need to change other modules has been rediced to
the minium, the IPA upstream code can simply be used.

The passed back certificates [http,dirsrv,pkinit]_ca_cert are encoded using
encode_certificate.
2020-06-03 12:53:34 +02:00
Thomas Woerner
2408a9b7c6 ansible_ipa_server: New functions encode_certificate and decode_certificate
The encode_certificate and decode_certificate are needed to encode and
decode a certificate in the way that it can be passed back from a module
and imported back into a usable certificate in another module.

For newer IPA versions the certificate is normally an IPACertificate for
older IPA versions it is simply a bytes array. But in both cases it needs
to be converted not to break Ansible.
2020-06-03 12:53:20 +02:00
Thomas Woerner
0372fec0e3 ca-less: No pre-generated certificates, generate them for each run
The certificates should not be pre-generated as they will expire at some
point. Simply generate them for each test run using the domain used in the
test. Copy the certificate files each time into the test server after
removing the old ones.
2020-06-03 12:30:06 +02:00
Samuel Veloso
07d7e2fa86 Generate mock certificates for ca-less installation 2020-06-03 12:14:17 +02:00
Samuel Veloso
4221213f1e Install ipaserver without ca 2020-06-03 12:14:17 +02:00
Rafael Guterres Jeffman
05a1aaed53 Fixes host absent when DNS zone is not found.
Since ipahost uses dnsrecord-show, it raises an error when DNS zone is
not found, but it should not be an ipahost concern.

This patch fixes this behavior by returning no record if DNS zone is
not found, so processing resumes as if there is no record for the host.
It fixes behavior when `state: absent` and dnszone does not exist, so,
host should not exist either, and the ipahost answer is correct and
indifferent to DNS Zone state.
2020-06-01 12:26:43 -03:00
Rafael Guterres Jeffman
5b53862871 Fixes no_log warning for update_password.
This patch explicitly set `no_log` option for `update_password` attribute
to `False`, so that the warning on `no_log` not being set is not issued
anymore. Ansible incorrectly issued the warning, as `update_password` does
not carry sensitive information.
2020-05-28 12:28:38 -03:00
Rafael Guterres Jeffman
7ca6c15fee Add missing attribute services to vault module.
The `services` member and ownership atttributes were missing from
vault module. This change adds them.

Handling of owner and ownergroups needed to be changed to fix `services`
and, due to this, have also been fixed.
2020-05-27 17:31:44 -03:00
Thomas Woerner
44af47d93a Merge pull request #254 from rjeffman/fix_vault_username_required
Fixes behavior of ipavault when no user, service or shared is given.
2020-05-27 16:16:13 +02:00
Sergio Oliveira Campos
89bc267d98 Fix all tests entry point
Running test_playbook_runs.py would result of running only the
last collected test but showing the name of the other tests instead.
To fix that the test_path was moved to an argument set by a method
decorator.
2020-05-26 11:53:53 -03:00
Sergio Oliveira
583d46b020 Merge pull request #274 from seocam/tests-entry-point
Added pytests as test entrypoint
2020-05-20 07:57:21 -03:00
Sergio Oliveira Campos
315f93c09a Added pytests as test entrypoint 2020-05-19 19:21:53 -03:00
Ivan Aragonés Muniesa
91094ce4d4 Update README.md
Added useful notes and the missing variable ipaserver_no_pkinit.
2020-05-14 17:31:05 +02:00
Ivan Aragonés Muniesa
848959ca6a Update README.md
Corrected variable names and description
2020-05-14 17:12:31 +02:00
Rafael Guterres Jeffman
c236fe3d62 Fixes behavior of ipavault when no user, service or shared is given.
IPA CLI allows the creation of vaults without specifying user, service or a
shared vault, defaulting to create a user vault for the `admin` user. The
vault module, required that one of user, service or shared was explicitly
provided, and this patch makes the module behave like the CLI command.

Tests were added to reflect this change.
2020-05-12 18:09:47 -03:00
Rafael Guterres Jeffman
bf15351c07 Merge pull request #262 from t-woerner/ipauser_fix_certmapdata
ipauser: Fix certmapdata, add missing certmapdata data option
2020-05-12 09:09:26 -03:00
Thomas Woerner
ac61f597d5 ipauser: Fix certmapdata, add missing certmapdata data option
certmapdata was not processed properly. The certificate was not loaded and
therefore the `issuer` and `subject` could not be compared to the
certmapdata entries in the user record. The function `load_cert_from_str`
from ansible_freeipa_moduleis used for this.

Additionally there was no way to use the certmapdata data format. This
is now possible with the `data` option in the certmapdata dict.

Example: "data: X509:<I>dc=com,dc=example,CN=ca<S>dc=com,dc=example,CN=test"

`data` may not be used together with `certificate`, `issuer` and `subject`
in the same record.

Given certmapdata for the ipauser module is now converted to the internal
data representation using also the new function `DN_x500_text` from
`ansible_freeipa_module`.

New functions `convert_certmapdata` and `check_certmapdata` have been added
to ipauser.

tests/user/certmapdata/test_user_certmapdata.yml has been extended with
additional tasks to verify more complex issuer and subjects and also using
the data format.
2020-05-12 13:31:52 +02:00
Thomas Woerner
fdcdad2c7e ansible_freeipa_module: New function api_check_command
This function can be used to check if a command is available in the API.

This is used in ipauser module to check if user_add_certmapdata is available
in the API.
2020-05-12 13:31:52 +02:00
Thomas Woerner
6a69bbeafb ansible_freeipa_module: New function DN_x500_text
This function is needed to properly convert issuer and subject from a
certificate or the issuer and subject parameters in ipauser for certmapdata
to the data representation where the items in DN are reversed.

The function additionally provides a fallback solution for IPA < 4.5.
Certmapdata is not supported for IPA < 4.5, but the conversion is done
before the API version can be checked.
2020-05-12 13:31:52 +02:00
Thomas Woerner
571cc210b5 ansible_freeipa_module: New function load_cert_from_str
For certmapdata processing in ipauser it is needed to be able to load a cert
from a string given in the task to be able to get the issuer and subject of
the certificate. The format of the certifiacte here is lacking the markers
for the begin and end of the certificate. Therefore load_pem_x509_certificate
can not be used directly. Also in IPA < 4.5 it is needed to load the
certificate with load_certificate instead of load_pem_x509_certificate. The
function is implementing this properly.
2020-05-12 13:31:52 +02:00
Thomas Woerner
a432c3ff50 Merge pull request #245 from rjeffman/fix_sudorule_categories
Fixes removal of `all` from categories in sudorule and hbacrule modules.
2020-05-12 13:06:18 +02:00
Rafael Guterres Jeffman
14d4502019 Merge pull request #261 from t-woerner/ipauser_encode_certificates
ipauser: Use encode_certificate for certificates in  find_user result
2020-05-11 20:55:13 -03:00
Rafael Guterres Jeffman
b0a067d5d5 Merge pull request #271 from t-woerner/fix_group_remove_member
ipagroup: Add lacking service check for group_remove_member with old IPA
2020-05-11 20:51:58 -03:00
Rafael Guterres Jeffman
f1c733d867 Merge pull request #270 from t-woerner/fix_test_hosts_principal_duplicates
tests/host/test_hosts_principal.yml: Remove dudplicate hosts tag
2020-05-11 20:49:28 -03:00
Rafael Guterres Jeffman
e36961f35e Merge pull request #269 from t-woerner/use_dnsrecord_show
ipahost: Use dnsrecord_show instead of dnsrecord_find command
2020-05-11 20:48:11 -03:00
Rafael Guterres Jeffman
e8317b281a Merge pull request #268 from t-woerner/fix_update_password_random
ipahost: Honour update_password also for random
2020-05-11 20:42:29 -03:00
Thomas Woerner
60c8be19a5 ipagroup: Add lacking service check for group_remove_member with old IPA
group_remove_member is not able to handle services in old IPA releases.
In one case the check was missing and the removal of a user from a group
failed because of this with an older IPA version. The missing check has
been added.

Fixes #257 (ipagroup fails to remove user from group ipausers)
2020-05-11 13:21:29 +02:00
Thomas Woerner
1f1762bd25 tests/host/test_hosts_principal.yml: Remove dudplicate hosts tag
The hosts tag is used twice in some tests. This leads to a warning in
Ansible. The commit removes the duplicate tags.
2020-05-11 13:20:11 +02:00
Thomas Woerner
2b084e6d15 ipahost: Use dnsrecord_show instead of dnsrecord_find command
The host_find command had to be replaced to get the "has_password" and
"has_keytab" return values. This commit replaces the dnsrecord_find
with the dnsrecord_show command to have consistent find functions in
the module.
2020-05-11 13:15:54 +02:00
Thomas Woerner
b3d5b32e31 ipahost: Honour update_password also for random
If random is enabled and update_password is limited to "create_only", the
random password may only be changed if the host does not exist yet.

Additionally the generation of the random password will fail, if the host
is already enrolled if update_password is "always" (default value). An
error will be reported early in this case now.

The command host_show is now used instead of host_find, as `has_password`
and `has_keytab` are only returned by host_show, but not by host_find. The
find_host function has been adapated for this change.

Resolves: #253 (ipahost is not idempotent)
2020-05-11 13:13:54 +02:00
Sergio Oliveira
67261c3dcd Merge pull request #256 from rjeffman/vault_fail_temp_kinit
Fixes usage of Kerberos credentials on Vault module.
2020-05-07 17:06:08 -03:00
Rafael Guterres Jeffman
84d8fc0cf3 Merge pull request #259 from t-woerner/do_not_remove_members
Do not remove member attributes while updating others
2020-05-07 09:43:55 -03:00
Thomas Woerner
791c4703b1 ipauser: Use encode_certificate for certificates in find_user result
The find_user function was not using encode_certificate for certificates
that are stored in the user record. This could lead to some issues with
older ipa releases and Python 2.
2020-05-06 17:40:22 +02:00
Thomas Woerner
457050c6ac Do not remove member attributes while updating others
Because of a missing check member attributes (for use with action: member)
are cleared when a non-member attribute is changed. The fix simply adds a
check for None (parameter not set) to gen_add_del_lists in
ansible_freeipa_module to make sure that the parameter is only changed if
it should be changed.

All places where the add and removal lists have been generated manually
have been changed to also use gen_add_del_lists.

Resolves: #252 (The "Manager" attribute is removed when updating any user
                attribute)
2020-05-06 17:04:14 +02:00
Rafael Guterres Jeffman
703ee1c9cd Fixes usage of Kerberos credentials on Vault module.
Even after obtaining Kerberos TGT with temp_kinit(), when connecting to
the IPA API with context `ansible-freeipa`, the API commands complained
that Kerberos credentials were not available. This patch fixes this
behavior.
2020-05-04 15:35:15 -03:00
Sergio Oliveira
efbc50b257 Merge pull request #250 from t-woerner/issue_249_no_root
ansible_freeipa_module: Set KRB5CCNAME for api_connect (non root)
2020-04-30 11:11:18 -03:00
Sergio Oliveira
cf1fe72616 Merge pull request #242 from seocam/lints
Add flake8 and pydocstyle lints
2020-04-29 16:40:10 -03:00
Sergio Oliveira Campos
6b0cf1e777 Doc string improvements 2020-04-25 19:07:54 -03:00
Sergio Oliveira Campos
0677af0714 Added azure-pipelines check 2020-04-25 19:07:54 -03:00
Sergio Oliveira Campos
5d7c0ec3d9 Fixed typo 2020-04-25 19:07:54 -03:00
Sergio Oliveira Campos
5643cfc20d Adjusted doc strings to follow PEP 257. 2020-04-25 19:07:54 -03:00
Sergio Oliveira Campos
4155f2f3ac Made code flake8 friendly 2020-04-25 19:07:54 -03:00
Thomas Woerner
871cce5258 ansible_freeipa_module: Set KRB5CCNAME for api_connect (non root)
In the case that the admin password has been set and become was not set
the call to backend.connect in api_connect failed. The solution is simply
to set os.environ["KRB5CCNAME"] in temp_kinit after kinit_password has
been called using the temporary ccache. os.environ["KRB5CCNAME"] is not
used automatically by api.Backend.[ldap2,rpcclient].connect. Afterwards
os.environ["KRB5CCNAME"] is unset in temp_kdestroy if ccache_name is not
None.

Fixes: #249 (Kerberos errors while using the modules with a non-sudoer user)
2020-04-16 17:00:22 +02:00
Rafael Guterres Jeffman
5e734e847e Fixes removal of all from HBAC rule categories.
This patch allows the removal of option `all` from user, host, and
service categories, by allowing an empty string as a valid choice
for each option.
2020-04-09 17:43:28 -03:00
Rafael Guterres Jeffman
9d348cb368 Fixes removal of all from sudorule categories.
This patch allows the removal of option `all` from user, host, group,
runasuser, and runasgroup categories, by allowing an empty string as
a valid choice for each option.
2020-04-09 17:40:32 -03:00
122 changed files with 8970 additions and 1040 deletions

149
README-config.md Normal file
View File

@@ -0,0 +1,149 @@
Config module
===========
Description
-----------
The config module allows the setting of global config parameters within IPA. If no parameters are specified it returns the list of all current parameters.
The config module is as compatible as possible to the Ansible upstream `ipa_config` module, but adds many additional parameters
Features
--------
* IPA server configuration management
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipaconfig module.
Requirements
------------
**Controller**
* Ansible version: 2.8+
**Node**
* Supported FreeIPA version (see above)
Usage
=====
Example inventory file
```ini
[ipaserver]
ipaserver.test.local
```
Example playbook to read config options:
```yaml
---
- name: Playbook to handle global config options
hosts: ipaserver
become: true
tasks:
- name: return current values of the global configuration options
ipaconfig:
ipaadmin_password: password
register: result
- name: display default login shell
debug:
msg: '{{result.config.defaultlogin }}'
- name: ensure defaultloginshell and maxusernamelength are set as required
ipaconfig:
ipaadmin_password: password
defaultlogin: /bin/bash
maxusername: 64
```
```yaml
---
- name: Playbook to ensure some config options are set
hosts: ipaserver
become: true
tasks:
- name: set defaultlogin and maxusername
ipaconfig:
ipaadmin_password: password
defaultlogin: /bin/bash
maxusername: 64
```
Variables
=========
ipauser
-------
**General Variables:**
Variable | Description | Required
-------- | ----------- | --------
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
`maxusername` \| `ipamaxusernamelength` | Set the maximum username length (1 to 255) | no
`maxhostname` \| `ipamaxhostnamelength` | Set the maximum hostname length between 64-255 | no
`homedirectory` \| `ipahomesrootdir` | Set the default location of home directories | no
`defaultshell` \| `ipadefaultloginshell` | Set the default shell for new users | no
`defaultgroup` \| `ipadefaultprimarygroup` | Set the default group for new users | no
`emaildomain`\| `ipadefaultemaildomain` | Set the default e-mail domain | false
`searchtimelimit` \| `ipasearchtimelimit` | Set maximum amount of time (seconds) for a search -1 to 2147483647 (-1 or 0 is unlimited) | no
`searchrecordslimit` \| `ipasearchrecordslimit` | Set maximum number of records to search -1 to 2147483647 (-1 or 0 is unlimited) | no
`usersearch` \| `ipausersearchfields` | Set list of fields to search when searching for users | no
`groupsearch` \| `ipagroupsearchfields` | Set list of fields to search in when searching for groups | no
`enable_migration` \| `ipamigrationenabled` | Enable migration mode (choices: True, False ) | no
`groupobjectclasses` \| `ipagroupobjectclasses` | Set default group objectclasses (list) | no
`userobjectclasses` \| `ipauserobjectclasses` | Set default user objectclasses (list) | no
`pwdexpnotify` \| `ipapwdexpadvnotify` | Set number of days's notice of impending password expiration (0 to 2147483647) | no
`configstring` \| `ipaconfigstring` | Set extra hashes to generate in password plug-in (choices:`AllowNThash`, `KDC:Disable Last Success`, `KDC:Disable Lockout`, `KDC:Disable Default Preauth for SPNs`). Use `""` to clear this variable. | no
`selinuxusermaporder` \| `ipaselinuxusermaporder`| Set ordered list in increasing priority of SELinux users | no
`selinuxusermapdefault`\| `ipaselinuxusermapdefault` | Set default SELinux user when no match is found in SELinux map rule | no
`pac_type` \| `ipakrbauthzdata` | set default types of PAC supported for services (choices: `MS-PAC`, `PAD`, `nfs:NONE`). Use `""` to clear this variable. | no
`user_auth_type` \| `ipauserauthtype` | set default types of supported user authentication (choices: `password`, `radius`, `otp`, `disabled`). Use `""` to clear this variable. | no
`domain_resolution_order` \| `ipadomainresolutionorder` | Set list of domains used for short name qualification | no
`ca_renewal_master_server` \| `ipacarenewalmasterserver`| Renewal master for IPA certificate authority. | no
Return Values
=============
Variable | Description | Returned When
-------- | ----------- | -------------
`config` | config dict <br />Fields: | No values to configure are specified
&nbsp; | `maxusername` | &nbsp;
&nbsp; | `maxhostname` | &nbsp;
&nbsp; | `homedirectory` | &nbsp;
&nbsp; | `defaultshell` | &nbsp;
&nbsp; | `defaultgroup` | &nbsp;
&nbsp; | `emaildomain` | &nbsp;
&nbsp; | `searchtimelimit` | &nbsp;
&nbsp; | `searchrecordslimit` | &nbsp;
&nbsp; | `usersearch` | &nbsp;
&nbsp; | `groupsearch` | &nbsp;
&nbsp; | `enable_migration` | &nbsp;
&nbsp; | `groupobjectclasses` | &nbsp;
&nbsp; | `userobjectclasses` | &nbsp;
&nbsp; | `pwdexpnotify` | &nbsp;
&nbsp; | `configstring` | &nbsp;
&nbsp; | `selinuxusermapdefault` | &nbsp;
&nbsp; | `selinuxusermaporder` | &nbsp;
&nbsp; | `pac_type` | &nbsp;
&nbsp; | `user_auth_type` | &nbsp;
&nbsp; | `domain_resolution_order` | &nbsp;
&nbsp; | `ca_renewal_master_server` | &nbsp;
All returned fields take the same form as their namesake input parameters
Authors
=======
Chris Procter

357
README-dnsrecord.md Normal file
View File

@@ -0,0 +1,357 @@
DNSRecord module
================
Description
-----------
The dnsrecord module allows management of DNS records and is as compatible as possible with the Ansible upstream `ipa_dnsrecord` module, but provide some other features like multiple record management in one execution and support for more DNS record types.
Features
--------
* DNS record management.
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipadnsrecord module.
Requirements
------------
**Controller**
* Ansible version: 2.8+
**Node**
* Supported FreeIPA version (see above)
Usage
=====
Example inventory file
```ini
[ipaserver]
ipaserver.example.com
```
Example playbook to ensure an AAAA record is present:
```yaml
---
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: example.com
record_type: 'AAAA'
record_value: '::1'
```
Example playbook to ensure an AAAA record is present, with a TTL of 300:
```yaml
---
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: example.com
record_type: 'AAAA'
record_value: '::1'
record_ttl: 300
```
Example playbook to ensure an AAAA record is present, with a reverse PTR record:
```yaml
---
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host02
zone_name: example.com
record_type: 'AAAA'
record_value: 'fd00::0002'
create_reverse: yes
```
Example playbook to ensure a LOC record is present, given its individual attributes:
```yaml
---
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: example.com
name: host03
loc_lat_deg: 52
loc_lat_min: 22
loc_lat_sec: 23.000
loc_lat_dir: N
loc_lon_deg: 4
loc_lon_min: 53
loc_lon_sec: 32.00
loc_lon_dir: E
loc_altitude: -2.00
loc_size: 1.00
loc_h_precision: 10000
loc_v_precision: 10
```
Example playbook to ensure multiple DNS records are present:
```yaml
---
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
records:
- name: host02
zone_name: example.com
record_type: A
record_value:
- "{{ ipv4_prefix }}.112"
- "{{ ipv4_prefix }}.122"
- name: host02
zone_name: example.com
record_type: AAAA
record_value: ::1
```
Example playbook to ensure multiple CNAME records are present:
```yaml
---
- name: Ensure that 'host03' and 'host04' have CNAME records.
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: example.com
records:
- name: host03
cname_hostname: host03.example.com
- name: host04
cname_hostname: host04.example.com
```
Example playbook to ensure NS record is absent:
```yaml
---
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: example.com
name: host04
ns_hostname: host04
state: absent
```
Example playbook to ensure LOC record is present, with fields:
```yaml
---
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: example.com
name: host04
loc_lat_deg: 52
loc_lat_min: 22
loc_lat_sec: 23.000
loc_lat_dir: N
loc_lon_deg: 4
loc_lon_min: 53
loc_lon_sec: 32.000
loc_lon_dir: E
loc_altitude: -2.00
loc_size: 0.00
loc_h_precision: 10000
loc_v_precision: 10
```
Change value of an existing LOC record:
```yaml
---
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: example.com
name: host04
loc_size: 1.00
loc_rec: 52 22 23 N 4 53 32 E -2 0 10000 10
```
Example playbook to ensure multiple A records are present:
```yaml
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: example.com
name: host04
a_rec:
- 192.168.122.221
- 192.168.122.222
- 192.168.122.223
- 192.168.122.224
```
Example playbook to ensure A and AAAA records are present, with reverse records (PTR):
```yaml
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: example.com
name: host01
a_rec:
- 192.168.122.221
- 192.168.122.222
aaaa_rec:
- fd00:;0001
- fd00::0002
create_reverse: yes
```
Example playbook to ensure multiple A and AAAA records are present, but only A records have reverse records:
```yaml
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: example.com
name: host01
a_ip_address: 192.168.122.221
aaaa_ip_address: fd00::0001
a_create_reverse: yes
```
Example playbook to ensure multiple DNS records are absent:
```yaml
---
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: example.com
records:
- name: host01
del_all: yes
- name: host02
del_all: yes
- name: host03
del_all: yes
- name: host04
del_all: yes
- name: _ftp._tcp
del_all: yes
- name: _sip._udp
del_all: yes
state: absent
```
Variables
=========
ipadnsrecord
------------
Variable | Description | Required
-------- | ----------- | --------
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
`zone_name` \| `dnszone` | The DNS zone name to which DNS record needs to be managed. You can use one global zone name for multiple records. | no
required: true
`records` | The list of dns records dicts. Each `records` dict entry can contain **record variables**. | no
&nbsp; | **Record variables** | no
**Record variables** | Used when defining a single record. | no
`state` | The state to ensure. It can be one of `present` or `absent`, and defaults to `present`. | yes
**Record Variables:**
Variable | Description | Required
-------- | ----------- | --------
`zone_name` \| `dnszone` | The DNS zone name to which DNS record needs to be managed. You can use one global zone name for multiple records. When used on a `records` dict, overrides the global `zone_name`. | yes
`name` \| `record_name` | The DNS record name to manage. | yes
`record_type` | The type of DNS record. Supported values are `A`, `AAAA`, `A6`, `AFSDB`, `CERT`, `CNAME`, `DLV`, `DNAME`, `DS`, `KX`, `LOC`, `MX`, `NAPTR`, `NS`, `PTR`, `SRV`, `SSHFP`, `TLSA`, `TXT`, `URI`, and defaults to `A`. | no
`record_value` | Manage DNS record name with this values. | no
`record_ttl` | Set the TTL for the record. (int) | no
`del_all` | Delete all associated records. (bool) | no
`a_rec` \| `a_record` | Raw A record. | no
`aaaa_rec` \| `aaaa_record` | Raw AAAA record. | no
`a6_rec` \| `a6_record` | Raw A6 record data. | no
`afsdb_rec` \| `afsdb_record` | Raw AFSDB record. | no
`cert_rec` \| `cert_record` | Raw CERT record. | no
`cname_rec` \| `cname_record` | Raw CNAME record. | no
`dlv_rec` \| `dlv_record` | Raw DLV record. | no
`dname_rec` \| `dname_record` | Raw DNAM record. | no
`ds_rec` \| `ds_record` | Raw DS record. | no
`kx_rec` \| `kx_record` | Raw KX record. | no
`loc_rec` \| `loc_record` | Raw LOC record. | no
`mx_rec` \| `mx_record` | Raw MX record. | no
`naptr_rec` \| `naptr_record` | Raw NAPTR record. | no
`ns_rec` \| `ns_record` | Raw NS record. | no
`ptr_rec` \| `ptr_record` | Raw PTR record. | no
`srv_rec` \| `srv_record` | Raw SRV record. | no
`sshfp_rec` \| `sshfp_record` | Raw SSHFP record. | no
`tlsa_rec` \| `tlsa_record` | Raw TLSA record. | no
`txt_rec` \| `txt_record` | Raw TXT record. | no
`uri_rec` \| `uri_record` | Raw URI record. | no
`ip_address` | IP adress for A or AAAA records. Set `record_type` to `A` or `AAAA`. | no
`create_reverse` \| `reverse` | Create reverse records for `A` and `AAAA` record types. There is no equivalent to remove reverse records. (bool) | no
`a_ip_address` | IP adress for A records. Set `record_type` to `A`. | no
`a_create_reverse` | Create reverse records only for `A` records. There is no equivalent to remove reverse records. (bool) | no
`aaaa_ip_address` | IP adress for AAAA records. Set `record_type` `AAAA`. | no
`aaaa_create_reverse` | Create reverse records only for `AAAA` record types. There is no equivalent to remove reverse records. (bool) | no
`a6_data` | A6 record. Set `record_type` to `A6`. | no
`afsdb_subtype` | AFSDB Subtype. Set `record_type` to `AFSDB`. (int) | no
`afsdb_hostname` | AFSDB Hostname. Set `record_type` to `AFSDB`. | no
`cert_type` | CERT Certificate Type. Set `record_type` to `CERT`. (int) | no
`cert_key_tag` | CERT Key Tag. Set `record_type` to `CERT`. (int) | no
`cert_algorithm` | CERT Algorithm. Set `record_type` to `CERT`. (int) | no
`cert_certificate_or_crl` | CERT Certificate or Certificate Revocation List (CRL). Set `record_type` to `CERT`. | no
`cname_hostname` | A hostname which this alias hostname points to. Set `record_type` to `CNAME`. | no
`dlv_key_tag` | DS Key Tag. Set `record_type` to `DLV`. (int) | no
`dlv_algorithm` | DLV Algorithm. Set `record_type` to `DLV`. (int) | no
`dlv_digest_type` | DLV Digest Type. Set `record_type` to `DLV`. (int) | no
`dlv_digest` | DLV Digest. Set `record_type` to `DLV`. | no
`dname_target` | DNAME Target. Set `record_type` to `DNAME`. | no
`ds_key_tag` | DS Key Tag. Set `record_type` to `DS`. (int) | no
`ds_algorithm` | DS Algorithm. Set `record_type` to `DS`. (int) | no
`ds_digest_type` | DS Digest Type. Set `record_type` to `DS`. (int) | no
`ds_digest` | DS Digest. Set `record_type` to `DS`. | no
`kx_preference` | Preference given to this exchanger. Lower values are more preferred. Set `record_type` to `KX`. (int) | no
`kx_exchanger` | A host willing to act as a key exchanger. Set `record_type` to `KX`. | no
`loc_lat_deg` | LOC Degrees Latitude. Set `record_type` to `LOC`. (int) | no
`loc_lat_min` | LOC Minutes Latitude. Set `record_type` to `LOC`. (int) | no
`loc_lat_sec` | LOC Seconds Latitude. Set `record_type` to `LOC`. (float) | no
`loc_lat_dir` | LOC Direction Latitude. Valid values are `N` or `S`. Set `record_type` to `LOC`. (int) | no
`loc_lon_deg` | LOC Degrees Longitude. Set `record_type` to `LOC`. (int) | no
`loc_lon_min` | LOC Minutes Longitude. Set `record_type` to `LOC`. (int) | no
`loc_lon_sec` | LOC Seconds Longitude. Set `record_type` to `LOC`. (float) | no
`loc_lon_dir` | LOC Direction Longitude. Valid values are `E` or `W`. Set `record_type` to `LOC`. (int) | no
`loc_altitude` | LOC Altitude. Set `record_type` to `LOC`. (float) | no
`loc_size` | LOC Size. Set `record_type` to `LOC`. (float) | no
`loc_h_precision` | LOC Horizontal Precision. Set `record_type` to `LOC`. (float) | no
`loc_v_precision` | LOC Vertical Precision. Set `record_type` to `LOC`. (float) | no
`mx_preference` | Preference given to this exchanger. Lower values are more preferred. Set `record_type` to `MX`. (int) | no
`mx_exchanger` | A host willing to act as a mail exchanger. Set `record_type` to `LOC`. | no
`naptr_order` | NAPTR Order. Set `record_type` to `NAPTR`. (int) | no
`naptr_preference` | NAPTR Preference. Set `record_type` to `NAPTR`. (int) | no
`naptr_flags` | NAPTR Flags. Set `record_type` to `NAPTR`. | no
`naptr_service` | NAPTR Service. Set `record_type` to `NAPTR`. | no
`naptr_regexp` | NAPTR Regular Expression. Set `record_type` to `NAPTR`. | no
`naptr_replacement` | NAPTR Replacement. Set `record_type` to `NAPTR`. | no
`ns_hostname` | NS Hostname. Set `record_type` to `NS`. | no
`ptr_hostname` | The hostname this reverse record points to. . Set `record_type` to `PTR`. | no
`srv_priority` | Lower number means higher priority. Clients will attempt to contact the server with the lowest-numbered priority they can reach. Set `record_type` to `SRV`. (int) | no
`srv_weight` | Relative weight for entries with the same priority. Set `record_type` to `SRV`. (int) | no
`srv_port` | SRV Port. Set `record_type` to `SRV`. (int) | no
`srv_target` | The domain name of the target host or '.' if the service is decidedly not available at this domain. Set `record_type` to `SRV`. | no
`sshfp_algorithm` | SSHFP Algorithm. Set `record_type` to `SSHFP`. (int) | no
`sshfp_fp_type` | SSHFP Fingerprint Type. Set `record_type` to `SSHFP`. (int) | no
`sshfp_fingerprint`| SSHFP Fingerprint. Set `record_type` to `SSHFP`. (int) | no
`txt_data` | TXT Text Data. Set `record_type` to `TXT`. | no
`tlsa_cert_usage` | TLSA Certificate Usage. Set `record_type` to `TLSA`. (int) | no
`tlsa_selector` | TLSA Selector. Set `record_type` to `TLSA`. (int) | no
`tlsa_matching_type` | TLSA Matching Type. Set `record_type` to `TLSA`. (int) | no
`tlsa_cert_association_data` | TLSA Certificate Association Data. Set `record_type` to `TLSA`. | no
`uri_target` | Target Uniform Resource Identifier according to RFC 3986. Set `record_type` to `URI`. | no
`uri_priority` | Lower number means higher priority. Clients will attempt to contact the URI with the lowest-numbered priority they can reach. Set `record_type` to `URI`. (int) | no
`uri_weight` | Relative weight for entries with the same priority. Set `record_type` to `URI`. (int) | no
Authors
=======
Rafael Guterres Jeffman

View File

@@ -143,6 +143,8 @@ Variable | Description | Required
`user` | List of user name strings assigned to this group. | no
`group` | List of group name strings assigned to this group. | no
`service` | List of service name strings assigned to this group. Only usable with IPA versions 4.7 and up. | no
`membermanager_user` | List of member manager users assigned to this group. Only usable with IPA versions 4.8.4 and up. | no
`membermanager_group` | List of member manager groups assigned to this group. Only usable with IPA versions 4.8.4 and up. | no
`action` | Work on group or member level. It can be on of `member` or `group` and defaults to `group`. | no
`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | yes

View File

@@ -138,9 +138,9 @@ Variable | Description | Required
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
`name` \| `cn` | The list of hbacrule name strings. | yes
`description` | The hbacrule description string. | no
`usercategory` \| `usercat` | User category the rule applies to. Choices: ["all"] | no
`hostcategory` \| `hostcat` | Host category the rule applies to. Choices: ["all"] | no
`servicecategory` \| `servicecat` | HBAC service category the rule applies to. Choices: ["all"] | no
`usercategory` \| `usercat` | User category the rule applies to. Choices: ["all", ""] | no
`hostcategory` \| `hostcat` | Host category the rule applies to. Choices: ["all", ""] | no
`servicecategory` \| `servicecat` | HBAC service category the rule applies to. Choices: ["all", ""] | no
`nomembers` | Suppress processing of membership attributes. (bool) | no
`host` | List of host name strings assigned to this hbacrule. | no
`hostgroup` | List of host group name strings assigned to this hbacrule. | no

View File

@@ -173,14 +173,14 @@ Example playbook to ensure host presence with a random password:
name: host01.example.com
random: yes
force: yes
update_password: on_create
register: ipahost
- name: Print generated random password
debug:
var: ipahost.host.randompassword
```
Please remember that the `force` tag will also force the generation of a new random password even if the host already exists and if `update_password` is limited to `on_create`.
Please remember that a new random password will be generated for an existing but not enrolled host if `update_password` is not limited to `on_create`. For an already enrolled host the task will fail with `update_password` default setting `always`.
Example playbook to ensure presence of several hosts with a random password:
@@ -198,9 +198,11 @@ Example playbook to ensure presence of several hosts with a random password:
- name: host01.example.com
random: yes
force: yes
update_password: on_create
- name: host02.example.com
random: yes
force: yes
update_password: on_create
register: ipahost
- name: Print generated random password for host01.example.com
@@ -211,7 +213,7 @@ Example playbook to ensure presence of several hosts with a random password:
debug:
var: ipahost.host["host02.example.com"].randompassword
```
Please remember that the `force` tag will also force the generation of a new random password even if the host alreay exists and if `update_password` is limited to `on_create`.
Please remember that a new random password will be generated for an existing but not enrolled host if `update_password` is not limited to `on_create`. For an already enrolled host the task will fail with `update_password` default setting `always`.
Example playbook to ensure presence of host member principal:
@@ -337,8 +339,8 @@ Variable | Description | Required
`location` \| `ns_host_location` | Host location (e.g. "Lab 2"). | no
`platform` \| `ns_hardware_platform` | Host hardware platform (e.g. "Lenovo T61"). | no
`os` \| `ns_os_version` | Host operating system and version (e.g. "Fedora 9"). | no
`password` \| `user_password` \| `userpassword` | Password used in bulk enrollment. | no
`random` \| `random_password` | Initiate the generation of a random password to be used in bulk enrollment. | no
`password` \| `user_password` \| `userpassword` | Password used in bulk enrollment for absent or not enrolled hosts. | no
`random` \| `random_password` | Initiate the generation of a random password to be used in bulk enrollment for absent or not enrolled hosts. | no
`certificate` \| `usercertificate` | List of base-64 encoded host certificates | no
`managedby` \| `principalname` \| `krbprincipalname` | List of hosts that can manage this host | no
`principal` \| `principalname` \| `krbprincipalname` | List of principal aliases for this host | no

View File

@@ -137,6 +137,8 @@ Variable | Description | Required
`nomembers` | Suppress processing of membership attributes. (bool) | no
`host` | List of host name strings assigned to this hostgroup. | no
`hostgroup` | List of hostgroup name strings assigned to this hostgroup. | no
`membermanager_user` | List of member manager users assigned to this hostgroup. Only usable with IPA versions 4.8.4 and up. | no
`membermanager_group` | List of member manager groups assigned to this hostgroup. Only usable with IPA versions 4.8.4 and up. | no
`action` | Work on hostgroup or member level. It can be on of `member` or `hostgroup` and defaults to `hostgroup`. | no
`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | no

View File

@@ -310,6 +310,7 @@ Variable | Description | Required
`allow_retrieve_keytab_group` \| `ipaallowedtoperform_read_keys_group` | Groups allowed to retrieve a keytab of this host. | no
`allow_retrieve_keytab_host` \| `ipaallowedtoperform_read_keys_host` | Hosts allowed to retrieve a keytab from of host. | no
`allow_retrieve_keytab_hostgroup` \| `ipaallowedtoperform_read_keys_hostgroup` | Host groups allowed to retrieve a keytab of this host. | no
`continue` | Continuous mode: don't stop on errors. Valid only if `state` is `absent`. Default: `no` (bool) | no
`action` | Work on service or member level. It can be on of `member` or `service` and defaults to `service`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, or `disabled`, default: `present`. | no

View File

@@ -122,11 +122,11 @@ Variable | Description | Required
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
`name` \| `cn` | The list of sudorule name strings. | yes
`description` | The sudorule description string. | no
`usercategory` | User category the rule applies to. Choices: ["all"] | no
`hostcategory` | Host category the rule applies to. Choices: ["all"] | no
`cmdcategory` | Command category the rule applies to. Choices: ["all"] | no
`runasusercategory` | RunAs User category the rule applies to. Choices: ["all"] | no
`runasgroupcategory` | RunAs Group category the rule applies to. Choices: ["all"] | no
`usercategory` \| `usercat` | User category the rule applies to. Choices: ["all", ""] | no
`hostcategory` \| `hostcat` | Host category the rule applies to. Choices: ["all", ""] | no
`cmdcategory` \| `cmdcat` | Command category the rule applies to. Choices: ["all", ""] | no
`runasusercategory` \| `rusasusercat` | RunAs User category the rule applies to. Choices: ["all", ""] | no
`runasgroupcategory` \| `runasgroupcat` | RunAs Group category the rule applies to. Choices: ["all", ""] | no
`nomembers` | Suppress processing of membership attributes. (bool) | no
`host` | List of host name strings assigned to this sudorule. | no
`hostgroup` | List of host group name strings assigned to this sudorule. | no

View File

@@ -417,10 +417,11 @@ Variable | Description | Required
`employeetype` | Employee Type | no
`preferredlanguage` | Preferred Language | no
`certificate` | List of base-64 encoded user certificates. | no
`certmapdata` | List of certificate mappings. Either `certificate` or `issuer` together with `subject` need to be specified. <br>Options: | no
&nbsp; | `certificate` - Base-64 encoded user certificate | no
&nbsp; | `issuer` - Issuer of the certificate | no
&nbsp; | `subject` - Subject of the certificate | no
`certmapdata` | List of certificate mappings. Either `data` or `certificate` or `issuer` together with `subject` need to be specified. Only usable with IPA versions 4.5 and up. <br>Options: | no
&nbsp; | `certificate` - Base-64 encoded user certificate, not usable with other certmapdata options. | no
&nbsp; | `issuer` - Issuer of the certificate, only usable together with `usbject` option. | no
&nbsp; | `subject` - Subject of the certificate, only usable together with `issuer` option. | no
&nbsp; | `data` - Certmap data, not usable with other certmapdata options. | no
`noprivate` | Do not create user private group. (bool) | no
`nomembers` | Suppress processing of membership attributes. (bool) | no

View File

@@ -41,7 +41,7 @@ Example inventory file
ipaserver.test.local
```
Example playbook to make sure vault is present:
Example playbook to make sure vault is present (by default, vault type is `symmetric`):
```yaml
---
@@ -53,8 +53,7 @@ Example playbook to make sure vault is present:
- ipavault:
ipaadmin_password: SomeADMINpassword
name: symvault
username: admin
vault_password: MyVaultPassword123
password: SomeVAULTpassword
description: A standard private vault.
```
@@ -124,13 +123,30 @@ Example playbook to make sure vault data is present in a symmetric vault:
ipaadmin_password: SomeADMINpassword
name: symvault
username: admin
vault_password: MyVaultPassword123
vault_data: >
password: SomeVAULTpassword
data: >
Data archived.
More data archived.
action: member
```
Example playbook to retrieve vault data from a symmetric vault:
```yaml
---
- name: Playbook to handle vaults
hosts: ipaserver
become: true
tasks:
- ipavault:
ipaadmin_password: SomeADMINpassword
name: symvault
username: admin
password: SomeVAULTpassword
state: retrieved
```
Example playbook to make sure vault data is absent in a symmetric vault:
```yaml
@@ -144,7 +160,7 @@ Example playbook to make sure vault data is absent in a symmetric vault:
ipaadmin_password: SomeADMINpassword
name: symvault
username: admin
vault_password: MyVaultPassword123
password: SomeVAULTpassword
action: member
state: absent
```
@@ -163,6 +179,9 @@ Example playbook to make sure vault is absent:
name: symvault
username: admin
state: absent
register: result
- debug:
msg: "{{ result.data }}"
```
Variables
@@ -178,17 +197,37 @@ Variable | Description | Required
`name` \| `cn` | The list of vault name strings. | yes
`description` | The vault description string. | no
`nomembers` | Suppress processing of membership attributes. (bool) | no
`vault_public_key` \| `ipavaultpublickey` | Vault public key. | no
`vault_salt` \| `ipavaultsalt` | Vault salt. | no
`password ` \| `vault_password` \| `ipavaultpassword` | Vault password. | no
`public_key ` \| `vault_public_key` \| `ipavaultpublickey` | Base64 encoded vault public key. | no
`public_key_file` \| `vault_public_key_file` | Path to file with public key. | no
`private_key `\| `vault_private_key` | Base64 encoded vault private key. Used only to retrieve data. | no
`private_key_file` \| `vault_private_key_file` | Path to file with private key. Used only to retrieve data. | no
`salt` \| `vault_salt` \| `ipavaultsalt` | Vault salt. | no
`vault_type` \| `ipavaulttype` | Vault types are based on security level. It can be one of `standard`, `symmetric` or `asymmetric`, default: `symmetric` | no
`user` \| `username` | Any user can own one or more user vaults. | no
`service` | Any service can own one or more service vaults. | no
`user` | Any user can own one or more user vaults. | no
`shared` | Vault is shared. Default to false. (bool) | no
`users` | Users that are members of the vault. | no
`groups` | Groups that are member of the vault. | no
`vault_data` \| `ipavaultdata` | Data to be stored in the vault. | no
`services` | Services that are member of the vault. | no
`data` \|`vault_data` \| `ipavaultdata` | Data to be stored in the vault. | no
`in` \| `datafile_in` | Path to file with data to be stored in the vault. | no
`out` \| `datafile_out` | Path to file to store data retrieved from the vault. | no
`action` | Work on vault or member level. It can be on of `member` or `vault` and defaults to `vault`. | no
`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | no
`state` | The state to ensure. It can be one of `present`, `absent` or `retrieved`, default: `present`. | no
Return Values
=============
ipavault
--------
There is only a return value if `state` is `retrieved`.
Variable | Description | Returned When
-------- | ----------- | -------------
`data` | The data stored in the vault. | If `state` is `retrieved`.
Notes

View File

@@ -12,6 +12,7 @@ Features
* One-time-password (OTP) support for client installation
* Repair mode for clients
* Modules for dns forwarder management
* Modules for dns record management
* Modules for dns zone management
* Modules for group management
* Modules for hbacrule management
@@ -411,6 +412,7 @@ Modules in plugin/modules
* [ipadnsconfig](README-dnsconfig.md)
* [ipadnsforwardzone](README-dnsforwardzone.md)
* [ipadnsrecord](README-dnsrecord.md)
* [ipadnszone](README-dnszone.md)
* [ipagroup](README-group.md)
* [ipahbacrule](README-hbacrule.md)

22
azure-pipelines.yml Normal file
View File

@@ -0,0 +1,22 @@
trigger:
- master
pool:
vmImage: 'ubuntu-18.04'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.6'
- script: python -m pip install --upgrade pip setuptools wheel
displayName: Install tools
- script: pip install pydocstyle flake8
displayName: Install dependencies
- script: flake8 .
displayName: Run flake8 checks
- script: pydocstyle .
displayName: Verify docstings

View File

@@ -13,7 +13,6 @@ issues: "https://github.com/freeipa/ansible-freeipa/issues"
readme: "README.md"
license: "GPL-3.0-or-later"
license_file: "COPYING"
dependencies:

View File

@@ -0,0 +1,14 @@
---
- name: Playbook to handle global DNS configuration
hosts: ipaserver
become: no
gather_facts: no
tasks:
- name: Query IPA global configuration
ipaconfig:
ipaadmin_password: SomeADMINpassword
register: serverconfig
- debug:
msg: "{{ serverconfig }}"

View File

@@ -0,0 +1,11 @@
---
- name: Playbook to handle global DNS configuration
hosts: ipaserver
become: no
gather_facts: no
tasks:
- name: set ca_renewal_master_server
ipaconfig:
ipaadmin_password: SomeADMINpassword
ca_renewal_master_server: carenewal.example.com

View File

@@ -0,0 +1,18 @@
---
- name: Test PTR Record is present.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Ensure a PTR record is present
- name: Ensure that 'host04' has A and AAAA records.
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: ipatest.local
records:
- name: host04
a_ip_address: 192.168.122.104
- name: host04
aaaa_ip_address: ::1
state: absent

View File

@@ -0,0 +1,17 @@
---
- name: Test PTR Record is present.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Ensure a PTR record is present
- name: Ensure that 'host04' has A and AAAA records.
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: ipatest.local
records:
- name: host04
a_ip_address: 192.168.122.104
- name: host04
aaaa_ip_address: ::1

View File

@@ -0,0 +1,13 @@
---
- name: Test CNAME Record is present.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Ensure that 'host04' has CNAME, with cname_hostname
- ipadnsrecord:
zone_name: example.com
name: host04
cname_hostname: host04.example.com
state: absent

View File

@@ -0,0 +1,12 @@
---
- name: Test CNAME Record is present.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Ensure that 'host04' has CNAME, with cname_hostname
- ipadnsrecord:
zone_name: example.com
name: host04
cname_hostname: host04.example.com

View File

@@ -0,0 +1,15 @@
---
- name: Ensure MX Record is present.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Ensure an MX record is absent
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: '@'
record_type: 'MX'
record_value: '1 mailserver.example.com'
zone_name: example.com
state: present

View File

@@ -0,0 +1,15 @@
---
- name: Test PTR Record is present.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Ensure a PTR record is present
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: 5
record_type: 'PTR'
record_value: 'internal.ipa.example.com'
zone_name: 2.168.192.in-addr.arpa
state: present

View File

@@ -0,0 +1,15 @@
---
- name: Test SRV Record is present.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Ensure a SRV record is present
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: _kerberos._udp.example.com
record_type: 'SRV'
record_value: '10 50 88 ipa.example.com'
zone_name: example.com
state: present

View File

@@ -0,0 +1,16 @@
---
- name: Test SSHFP Record is present.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Ensure a SSHFP record is present
# SSHFP fingerprint generated with `ssh-keygen -r host04.testzone.local`
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: example.com
name: host04
sshfp_algorithm: 1
sshfp_fp_type: 1
sshfp_fingerprint: d21802c61733e055b8d16296cbce300efb8a167a

View File

@@ -0,0 +1,16 @@
---
- name: Test SSHFP Record is present.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Ensure a SSHFP record is present
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: example.com
name: host04
tlsa_cert_usage: 3
tlsa_selector: 1
tlsa_matching_type: 1
tlsa_cert_association_data: 9c0ad776dbeae8d9d55b0ad42899d30235c114d5f918fd69746e4279e47bdaa2

View File

@@ -0,0 +1,15 @@
---
- name: Test TXT Record is present.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Ensure a TXT record is absent
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: _kerberos
record_type: 'TXT'
record_value: 'EXAMPLE.COM'
zone_name: example.com
state: present

View File

@@ -0,0 +1,17 @@
---
- name: Test URI Record is present.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Ensure a URI record is absent
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: _ftp._tcp
record_type: 'URI'
uri_priority: 10
uri_weight: 1
uri_target: ftp://ftp.example.com/public
zone_name: example.com
state: present

View File

@@ -0,0 +1,15 @@
---
- name: Test DNS Record is absent.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Ensure that dns record is absent
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: example.com
record_type: 'AAAA'
record_value: '::1'
state: absent

View File

@@ -0,0 +1,15 @@
---
- name: Test DNS Record is present.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Ensure that dns record is present
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: example.com
record_type: 'AAAA'
record_value: '::1'
state: present

View File

@@ -0,0 +1,15 @@
---
- name: Test DNS Record is present.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Ensure that dns record is present
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: example.com
ip_address: 192.160.123.45
create_reverse: yes
state: present

View File

@@ -0,0 +1,17 @@
---
- name: Playbook to manage DNS records.
hosts: ipaserver
become: yes
gather_facts: no
tasks:
- name: Ensure that 'host04' has multiple A records.
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: ipatest.local
name: host01
a_rec:
- 192.168.122.221
- 192.168.122.222
- 192.168.122.223
- 192.168.122.224

View File

@@ -0,0 +1,21 @@
---
- name: Test multiple DNS Records are present.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Ensure that multiple dns records are present
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
records:
- name: host01
zone_name: example.com
record_type: A
record_value:
- 192.168.122.112
- 192.168.122.122
- name: host01
zone_name: testzone.local
record_type: AAAA
record_value: ::1

View File

@@ -0,0 +1,18 @@
---
- name: Playbook to change password of symmetric vault.
hosts: ipaserver
become: yes
gather_facts: no
tasks:
- name: Create vault.
ipavault:
ipaadmin_password: SomeADMINpassword
name: symvault
password: SomeVAULTpassword
- name: Change vault passord.
ipavault:
ipaadmin_password: SomeADMINpassword
name: symvault
password: SomeVAULTpassword
new_password: SomeNEWpassword

View File

@@ -9,6 +9,6 @@
ipaadmin_password: SomeADMINpassword
name: symvault
username: admin
vault_password: MyVaultPassword123
vault_password: SomeVAULTpassword
vault_data: The world of π is half rounded.
action: member

View File

@@ -9,5 +9,4 @@
ipaadmin_password: SomeADMINpassword
name: symvault
username: admin
vault_password: MyVaultPassword123
vault_type: symmetric
vault_password: SomeVAULTpassword

View File

@@ -0,0 +1 @@
SomeVAULTpassword

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEArM5/f6dd/YIm/a9eoGVTW8jobEgrf9PXRA3aHsA7kJo6fB18
HD4+RVUwx/lqlkPYbUi9bXV/rJAkUwAEDOnJeqXESZ+gVCVmigRzmKWK2ad9agmY
SiqyyNxFIJvZAo0dG4CAWjYK27tLg4Ih6oGsZIDG+WVES5W89K+L0bwVjq4tshhe
DMO57unvmIKEmaBE0ewPfvkdZh5k8Gts9H4fh0fGk5tbIYa0bhwMUpL+WHOm6nbd
+n7BbaVc820TgZDO/rSYtnuXaIc6Wx0U9LXZkUmk3apMnzknNaTqguAQdTn79G8P
qrGqmyWd/E1cH2b5jzIxiGo8psL5sxWVY7WJdwIDAQABAoIBAA6e9iit14UAgx4J
vX7is9fbOtcWkB+jo94NMfxSFXgZpIMl139oQMqK97KjxsHqAaDVe7mMLH5EP96J
7M3O5g4rgl0cVWtpMrDQyZsLvqDFzBWxtCHqVPAruumUZhsSJ3lROQro8ag/w5bf
5tC5ogVq4+rsB4hBphgp1jGrsUM+E8O7DXXFH68F8WgBi725WvcjnbI9irkb0Gcq
1bCPJwN3fA1i2VWiRwVYWbNTWnDoNM9ZdYYxK0kuUkD+QtreycWPf9V49lvUi1Vp
FVNmBUDvGK3K1MwbgXRwOXhacY7Ptjkdvaeb2Qcu5RjTkruGhzUYsOP3p/cw+wKV
vzQqceECgYEA5Wz7V2SlRa2r//z+ETQkJfENJ0KDnCb0pMClCQh3jTNPA6DbhiMk
FTkcoNbqcpTiVSlvhh6TKscSgqYQUjQ/OqyG7SkjKVjQ72j5beQLxiLTtUyj1OmP
Xh9cWJXx8iQ+45cPon+kMOAIiTwiB3mmFRfQjIGve1DPUo9J+NZ4XdECgYEAwNKg
OdGYxxKtCrXVz1mdg6PDlV8qh7nxxZbPch+aMIQl1+oTCgSiw8oOYEd8g0HOdV6t
1G+IWhvPxiiWy3/AE0QhgoKk2GUsSjWSMLcJbaUzDoEHFjTLjecRlqdzo7qxRXqB
meN4L5WJYKnLC482K7hvufS+uo5fB5qwPmt13McCgYAe4TVPRP+tyjttYCr+O8tl
w/UmRKCcQu4Iwtkzxwz4V2CaN2t0uYQgyygcSfESbRGtrr8RCUp7poHKTfnCZr/f
8NrUTwYpiYfNwY5ZCSnAiG2AaIlgnfMrEwOF9OC028YPMgTrtUxvO6hKeGqIIQqG
qkbqsoXhDjZpgVnOgWeAEQKBgGuiZ0w/IqAlXbC31fUb2iBMfvXXnJ8M/dfFGmFj
IKfqbFF9WUljUxQlqya1YNzIFB5STohiBeP+2FmN+Lb5xdc7VdVLZgdhWnrGMqe8
1Kd+6uQyxCjyKZo5nQjSymtf4GqfOs8TOdieCYSK40u9koiPONa9tuXeaU+OWslN
JQqrAoGBAJ3MKOvsnQzuZVP2vz0ZqLwIE3XjRiFGveVpizq4hwOVeuNsV08JvA0t
pueNIy9klPScFc9OUdiZWkEX09BwJkVIrOHotuSB8AStO5UAntNnuyWLJEFC4Uq4
GpB8lbj9jkxSKaU7X3Gac23K9JL8euLh7E7rPuZRYa6mYN4nbKqu
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArM5/f6dd/YIm/a9eoGVT
W8jobEgrf9PXRA3aHsA7kJo6fB18HD4+RVUwx/lqlkPYbUi9bXV/rJAkUwAEDOnJ
eqXESZ+gVCVmigRzmKWK2ad9agmYSiqyyNxFIJvZAo0dG4CAWjYK27tLg4Ih6oGs
ZIDG+WVES5W89K+L0bwVjq4tshheDMO57unvmIKEmaBE0ewPfvkdZh5k8Gts9H4f
h0fGk5tbIYa0bhwMUpL+WHOm6nbd+n7BbaVc820TgZDO/rSYtnuXaIc6Wx0U9LXZ
kUmk3apMnzknNaTqguAQdTn79G8PqrGqmyWd/E1cH2b5jzIxiGo8psL5sxWVY7WJ
dwIDAQAB
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,17 @@
---
- name: Tests
hosts: ipaserver
become: no
gather_facts: no
tasks:
- name: Retrieve data from assymetric vault with a private key file.
ipavault:
ipaadmin_password: SomeADMINpassword
name: asymvault
username: user01
private_key_file: private.pem
state: retrieved
register: result
- debug:
msg: "Data: {{ result.data }}"

View File

@@ -0,0 +1,17 @@
---
- name: Tests
hosts: ipaserver
become: no
gather_facts: no
tasks:
- name: Retrieve data from symmetric vault.
ipavault:
ipaadmin_password: SomeADMINpassword
name: symvault
username: admin
password: SomeVAULTpassword
state: retrieved
register: result
- debug:
msg: "{{ result.data | b64decode }}"

View File

@@ -0,0 +1,22 @@
---
- name: Tests
hosts: ipaserver
become: true
gather_facts: True
tasks:
- copy:
src: "{{ playbook_dir }}/password.txt"
dest: "{{ ansible_env.HOME }}/password.txt"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: 0600
- ipavault:
ipaadmin_password: SomeADMINpassword
name: symvault
username: admin
vault_type: symmetric
vault_password_file: "{{ ansible_env.HOME }}/password.txt"
- file:
path: "{{ ansible_env.HOME }}/password.txt"
state: absent

View File

@@ -0,0 +1,27 @@
---
#
# Example keys for this playbook were generated with the commands:
# $ openssl genrsa -out private.pem 2048
# $ openssl rsa -in private.pem -pubout > public.pem
#
- name: Tests
hosts: ipaserver
become: true
gather_facts: True
tasks:
- copy:
src: "{{ playbook_dir }}/public.pem"
dest: "{{ ansible_env.HOME }}/public.pem"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: 0600
- ipavault:
ipaadmin_password: SomeADMINpassword
name: asymvault
username: admin
vault_type: asymmetric
vault_public_key_file: "{{ ansible_env.HOME }}/public.pem"
- file:
path: "{{ ansible_env.HOME }}/public.pem"
state: absent

View File

@@ -39,6 +39,7 @@ try:
except ImportError:
from ipapython.ipautil import kinit_password, kinit_keytab
from ipapython.ipautil import run
from ipapython.dn import DN
from ipaplatform.paths import paths
from ipalib.krb_utils import get_credentials_if_valid
from ansible.module_utils.basic import AnsibleModule
@@ -48,6 +49,13 @@ try:
from ipalib.x509 import Encoding
except ImportError:
from cryptography.hazmat.primitives.serialization import Encoding
try:
from ipalib.x509 import load_pem_x509_certificate
except ImportError:
from ipalib.x509 import load_certificate
load_pem_x509_certificate = None
import socket
import base64
import six
@@ -63,9 +71,7 @@ if six.PY3:
def valid_creds(module, principal): # noqa
"""
Get valid credintials matching the princial, try GSSAPI first
"""
"""Get valid credentials matching the princial, try GSSAPI first."""
if "KRB5CCNAME" in os.environ:
ccache = os.environ["KRB5CCNAME"]
module.debug('KRB5CCNAME set to %s' % ccache)
@@ -103,9 +109,7 @@ def valid_creds(module, principal): # noqa
def temp_kinit(principal, password):
"""
kinit with password using a temporary ccache
"""
"""Kinit with password using a temporary ccache."""
if not password:
raise RuntimeError("The password is not set")
if not principal:
@@ -119,22 +123,27 @@ def temp_kinit(principal, password):
except RuntimeError as e:
raise RuntimeError("Kerberos authentication failed: {}".format(e))
os.environ["KRB5CCNAME"] = ccache_name
return ccache_dir, ccache_name
def temp_kdestroy(ccache_dir, ccache_name):
"""
Destroy temporary ticket and remove temporary ccache
"""
"""Destroy temporary ticket and remove temporary ccache."""
if ccache_name is not None:
run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False)
del os.environ['KRB5CCNAME']
if ccache_dir is not None:
shutil.rmtree(ccache_dir, ignore_errors=True)
def api_connect(context=None):
"""
Create environment, initialize api and connect to ldap2
Initialize IPA API with the provided context.
`context` can be any of:
* `server` (default)
* `ansible-freeipa`
* `cli_installer`
"""
env = Env()
env._bootstrap()
@@ -153,32 +162,33 @@ def api_connect(context=None):
backend = api.Backend.rpcclient
if not backend.isconnected():
backend.connect()
backend.connect(ccache=os.environ.get('KRB5CCNAME', None))
def api_command(module, command, name, args):
"""
Call ipa.Command
"""
"""Call ipa.Command."""
return api.Command[command](name, **args)
def api_command_no_name(module, command, args):
"""
Call ipa.Command without a name.
"""
"""Call ipa.Command without a name."""
return api.Command[command](**args)
def api_check_command(command):
"""Return if command exists in command list."""
return command in api.Command
def api_check_param(command, name):
"""
Return if param exists in command param list
"""
"""Check if param exists in command param list."""
return name in api.Command[command].params
def execute_api_command(module, principal, password, command, name, args):
"""
Execute an API command.
Get KRB ticket if not already there, initialize api, connect,
execute command and destroy ticket again if it has been created also.
"""
@@ -300,10 +310,11 @@ def api_get_realm():
def gen_add_del_lists(user_list, res_list):
"""
Generate the lists for the addition and removal of members using the
provided user and ipa settings
"""
"""Generate the lists for the addition and removal of members."""
# The user list is None, therefore the parameter should not be touched
if user_list is None:
return [], []
add_list = list(set(user_list or []) - set(res_list or []))
del_list = list(set(res_list or []) - set(user_list or []))
@@ -312,8 +323,9 @@ def gen_add_del_lists(user_list, res_list):
def encode_certificate(cert):
"""
Encode a certificate using base64 with also taking FreeIPA and Python
versions into account
Encode a certificate using base64.
It also takes FreeIPA and Python versions into account.
"""
if isinstance(cert, (str, unicode, bytes)):
encoded = base64.b64encode(cert)
@@ -324,6 +336,30 @@ def encode_certificate(cert):
return encoded
def load_cert_from_str(cert):
cert = cert.strip()
if not cert.startswith("-----BEGIN CERTIFICATE-----"):
cert = "-----BEGIN CERTIFICATE-----\n" + cert
if not cert.endswith("-----END CERTIFICATE-----"):
cert += "\n-----END CERTIFICATE-----"
if load_pem_x509_certificate is not None:
cert = load_pem_x509_certificate(cert.encode('utf-8'))
else:
cert = load_certificate(cert.encode('utf-8'))
return cert
def DN_x500_text(text):
if hasattr(DN, "x500_text"):
return DN(text).x500_text()
else:
# Emulate x500_text
dn = DN(text)
dn.rdns = reversed(dn.rdns)
return str(dn)
def is_valid_port(port):
if not isinstance(port, int):
return False
@@ -335,9 +371,7 @@ def is_valid_port(port):
def is_ipv4_addr(ipaddr):
"""
Test if figen IP address is a valid IPv4 address
"""
"""Test if given IP address is a valid IPv4 address."""
try:
socket.inet_pton(socket.AF_INET, ipaddr)
except socket.error:
@@ -346,9 +380,7 @@ def is_ipv4_addr(ipaddr):
def is_ipv6_addr(ipaddr):
"""
Test if figen IP address is a valid IPv6 address
"""
"""Test if given IP address is a valid IPv6 address."""
try:
socket.inet_pton(socket.AF_INET6, ipaddr)
except socket.error:

View File

@@ -0,0 +1,479 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Authors:
# Chris Procter <cprocter@redhat.com>
#
# Copyright (C) 2020 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/>.
ANSIBLE_METADATA = {
"metadata_version": "1.0",
"supported_by": "community",
"status": ["preview"],
}
DOCUMENTATION = '''
---
module: ipa_config
author: chris procter
short_description: Modify IPA global config options
description:
- Modify IPA global config options
options:
ipaadmin_principal:
description: The admin principal
default: admin
ipaadmin_password:
description: The admin password
required: false
maxusername:
description: Set the maximum username length between 1-255
required: false
aliases: ['ipamaxusernamelength']
maxhostname:
description: Set the maximum hostname length between 64-255
required: false
aliases: ['ipamaxhostnamelength']
homedirectory:
description: Set the default location of home directories
required: false
aliases: ['ipahomesrootdir']
defaultshell:
description: Set the default shell for new users
required: false
aliases: ['ipadefaultloginshell', 'loginshell']
defaultgroup:
description: Set the default group for new users
required: false
aliases: ['ipadefaultprimarygroup']
emaildomain:
description: Set the default e-mail domain
required: false
aliases: ['ipadefaultemaildomain']
searchtimelimit:
description:
- Set maximum amount of time (seconds) for a search
- values -1 to 2147483647 (-1 or 0 is unlimited)
required: false
aliases: ['ipasearchtimelimit']
searchrecordslimit:
description:
- Set maximum number of records to search
- values -1 to 2147483647 (-1 or 0 is unlimited)
required: false
aliases: ['ipasearchrecordslimit']
usersearch:
description:
- Set comma-separated list of fields to search for user search
required: false
aliases: ['ipausersearchfields']
groupsearch:
description:
- Set comma-separated list of fields to search for group search
required: false
aliases: ['ipagroupsearchfields']
enable_migration:
description: Enable migration mode
type: bool
required: false
aliases: ['ipamigrationenabled']
groupobjectclasses:
description: Set default group objectclasses (comma-separated list)
required: false
type: list
aliases: ['ipagroupobjectclasses']
userobjectclasses:
description: Set default user objectclasses (comma-separated list)
required: false
type: list
aliases: ['ipauserobjectclasses']
pwdexpnotify:
description:
- Set number of days's notice of impending password expiration
- values 0 to 2147483647
required: false
aliases: ['ipapwdexpadvnotify']
configstring:
description: Set extra hashes to generate in password plug-in
required: false
type: list
choices:
- "AllowNThash"
- "KDC:Disable Last Success"
- "KDC:Disable Lockout"
- "KDC:Disable Default Preauth for SPNs"
- ""
aliases: ['ipaconfigstring']
selinuxusermaporder:
description: Set order in increasing priority of SELinux users
required: false
type: list
aliases: ['ipaselinuxusermaporder']
selinuxusermapdefault:
description: Set default SELinux user when no match found in map rule
required: false
aliases: ['ipaselinuxusermapdefault']
pac_type:
description: set default types of PAC supported for services
required: false
type: list
choices: ["MS-PAC", "PAD", "nfs:NONE", ""]
aliases: ["ipakrbauthzdata"]
user_auth_type:
description: set default types of supported user authentication
required: false
type: list
choices: ["password", "radius", "otp", "disabled", ""]
aliases: ["ipauserauthtype"]
ca_renewal_master_server:
description: Renewal master for IPA certificate authority.
required: false
type: string
domain_resolution_order:
description: set list of domains used for short name qualification
required: false
type: list
aliases: ["ipadomainresolutionorder"]
'''
EXAMPLES = '''
---
- name: Playbook to handle global configuration options
hosts: ipaserver
become: true
tasks:
- name: return current values of the global configuration options
ipaconfig:
ipaadmin_password: password
register: result
- name: display default login shell
debug:
msg: '{{result.config.defaultshell[0] }}'
- name: set defaultshell and maxusername
ipaconfig:
ipaadmin_password: password
defaultshell: /bin/bash
maxusername: 64
'''
RETURN = '''
config:
description: Dict of all global config options
returned: When no options are set
type: dict
options:
maxusername:
description: maximum username length
returned: always
maxhostname:
description: maximum hostname length
returned: always
homedirectory:
description: default location of home directories
returned: always
defaultshell:
description: default shell for new users
returned: always
defaultgroup:
description: default group for new users
returned: always
emaildomain:
description: default e-mail domain
returned: always
searchtimelimit:
description: maximum amount of time (seconds) for a search
returned: always
searchrecordslimit:
description: maximum number of records to search
returned: always
usersearch:
description: comma-separated list of fields to search in user search
type: list
returned: always
groupsearch:
description: comma-separated list of fields to search in group search
type: list
returned: always
enable_migration:
description: Enable migration mode
type: bool
returned: always
groupobjectclasses:
description: default group objectclasses (comma-separated list)
type: list
returned: always
userobjectclasses:
description: default user objectclasses (comma-separated list)
type: list
returned: always
pwdexpnotify:
description: number of days's notice of impending password expiration
returned: always
configstring:
description: extra hashes to generate in password plug-in
type: list
returned: always
selinuxusermaporder:
description: order in increasing priority of SELinux users
returned: always
selinuxusermapdefault:
description: default SELinux user when no match is found in map rule
returned: always
pac_type:
description: default types of PAC supported for services
type: list
returned: always
user_auth_type:
description: default types of supported user authentication
returned: always
ca_renewal_master_server:
description: master for IPA certificate authority.
returned: always
domain_resolution_order:
description: list of domains used for short name qualification
returned: always
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
temp_kdestroy, valid_creds, api_connect, api_command_no_name, \
compare_args_ipa, module_params_get
import ipalib.errors
def config_show(module):
_result = api_command_no_name(module, "config_show", {})
return _result["result"]
def gen_args(params):
_args = {}
for k, v in params.items():
if v is not None:
_args[k] = v
return _args
def main():
ansible_module = AnsibleModule(
argument_spec=dict(
# general
ipaadmin_principal=dict(type="str", default="admin"),
ipaadmin_password=dict(type="str", required=False, no_log=True),
maxusername=dict(type="int", required=False,
aliases=['ipamaxusernamelength']),
maxhostname=dict(type="int", required=False,
aliases=['ipamaxhostnamelength']),
homedirectory=dict(type="str", required=False,
aliases=['ipahomesrootdir']),
defaultshell=dict(type="str", required=False,
aliases=['ipadefaultloginshell',
'loginshell']),
defaultgroup=dict(type="str", required=False,
aliases=['ipadefaultprimarygroup']),
emaildomain=dict(type="str", required=False,
aliases=['ipadefaultemaildomain']),
searchtimelimit=dict(type="int", required=False,
aliases=['ipasearchtimelimit']),
searchrecordslimit=dict(type="int", required=False,
aliases=['ipasearchrecordslimit']),
usersearch=dict(type="list", required=False,
aliases=['ipausersearchfields']),
groupsearch=dict(type="list", required=False,
aliases=['ipagroupsearchfields']),
enable_migration=dict(type="bool", required=False,
aliases=['ipamigrationenabled']),
groupobjectclasses=dict(type="list", required=False,
aliases=['ipagroupobjectclasses']),
userobjectclasses=dict(type="list", required=False,
aliases=['ipauserobjectclasses']),
pwdexpnotify=dict(type="int", required=False,
aliases=['ipapwdexpadvnotify']),
configstring=dict(type="list", required=False,
aliases=['ipaconfigstring'],
choices=["AllowNThash",
"KDC:Disable Last Success",
"KDC:Disable Lockout",
"KDC:Disable Default Preauth for SPNs",
""]), # noqa E128
selinuxusermaporder=dict(type="list", required=False,
aliases=['ipaselinuxusermaporder']),
selinuxusermapdefault=dict(type="str", required=False,
aliases=['ipaselinuxusermapdefault']),
pac_type=dict(type="list", required=False,
aliases=["ipakrbauthzdata"],
choices=["MS-PAC", "PAD", "nfs:NONE", ""]),
user_auth_type=dict(type="list", required=False,
choices=["password", "radius", "otp",
"disabled", ""],
aliases=["ipauserauthtype"]),
ca_renewal_master_server=dict(type="str", required=False),
domain_resolution_order=dict(type="list", required=False,
aliases=["ipadomainresolutionorder"])
),
supports_check_mode=True,
)
ansible_module._ansible_debug = True
# Get parameters
# general
ipaadmin_principal = module_params_get(ansible_module,
"ipaadmin_principal")
ipaadmin_password = module_params_get(ansible_module,
"ipaadmin_password")
field_map = {
"maxusername": "ipamaxusernamelength",
"maxhostname": "ipamaxhostnamelength",
"homedirectory": "ipahomesrootdir",
"defaultshell": "ipadefaultloginshell",
"defaultgroup": "ipadefaultprimarygroup",
"emaildomain": "ipadefaultemaildomain",
"searchtimelimit": "ipasearchtimelimit",
"searchrecordslimit": "ipasearchrecordslimit",
"usersearch": "ipausersearchfields",
"groupsearch": "ipagroupsearchfields",
"enable_migration": "ipamigrationenabled",
"groupobjectclasses": "ipagroupobjectclasses",
"userobjectclasses": "ipauserobjectclasses",
"pwdexpnotify": "ipapwdexpadvnotify",
"configstring": "ipaconfigstring",
"selinuxusermaporder": "ipaselinuxusermaporder",
"selinuxusermapdefault": "ipaselinuxusermapdefault",
"pac_type": "ipakrbauthzdata",
"user_auth_type": "ipauserauthtype",
"ca_renewal_master_server": "ca_renewal_master_server",
"domain_resolution_order": "ipadomainresolutionorder"
}
reverse_field_map = {v: k for k, v in field_map.items()}
params = {}
for x in field_map.keys():
val = module_params_get(ansible_module, x)
if val is not None:
params[field_map.get(x, x)] = val
if params.get("ipamigrationenabled") is not None:
params["ipamigrationenabled"] = \
str(params["ipamigrationenabled"]).upper()
if params.get("ipaselinuxusermaporder", None):
params["ipaselinuxusermaporder"] = \
"$".join(params["ipaselinuxusermaporder"])
if params.get("ipadomainresolutionorder", None):
params["ipadomainresolutionorder"] = \
":".join(params["ipadomainresolutionorder"])
if params.get("ipausersearchfields", None):
params["ipausersearchfields"] = \
",".join(params["ipausersearchfields"])
if params.get("ipagroupsearchfields", None):
params["ipagroupsearchfields"] = \
",".join(params["ipagroupsearchfields"])
# verify limits on INT values.
args_with_limits = [
("ipamaxusernamelength", 1, 255),
("ipamaxhostnamelength", 64, 255),
("ipasearchtimelimit", -1, 2147483647),
("ipasearchrecordslimit", -1, 2147483647),
("ipapwdexpadvnotify", 0, 2147483647),
]
for arg, min, max in args_with_limits:
if arg in params and (params[arg] > max or params[arg] < min):
ansible_module.fail_json(
msg="Argument '%s' must be between %d and %d."
% (arg, min, max))
changed = False
exit_args = {}
ccache_dir = None
ccache_name = None
res_show = None
try:
if not valid_creds(ansible_module, ipaadmin_principal):
ccache_dir, ccache_name = temp_kinit(ipaadmin_principal,
ipaadmin_password)
api_connect()
if params:
res_show = config_show(ansible_module)
params = {
k: v for k, v in params.items()
if k not in res_show or res_show[k] != v
}
if params \
and not compare_args_ipa(ansible_module, params, res_show):
changed = True
api_command_no_name(ansible_module, "config_mod", params)
else:
rawresult = api_command_no_name(ansible_module, "config_show", {})
result = rawresult['result']
del result['dn']
for key, v in result.items():
k = reverse_field_map.get(key, key)
if ansible_module.argument_spec.get(k):
if k == 'ipaselinuxusermaporder':
exit_args['ipaselinuxusermaporder'] = \
result.get(key)[0].split('$')
elif k == 'domain_resolution_order':
exit_args['domain_resolution_order'] = \
result.get(key)[0].split('$')
elif k == 'usersearch':
exit_args['usersearch'] = \
result.get(key)[0].split(',')
elif k == 'groupsearch':
exit_args['groupsearch'] = \
result.get(key)[0].split(',')
elif isinstance(v, str) and \
ansible_module.argument_spec[k]['type'] == "list":
exit_args[k] = [v]
elif isinstance(v, list) and \
ansible_module.argument_spec[k]['type'] == "str":
exit_args[k] = ",".join(v)
elif isinstance(v, list) and \
ansible_module.argument_spec[k]['type'] == "int":
exit_args[k] = ",".join(v)
elif isinstance(v, list) and \
ansible_module.argument_spec[k]['type'] == "bool":
exit_args[k] = (v[0] == "TRUE")
else:
exit_args[k] = v
except ipalib.errors.EmptyModlist:
changed = False
except Exception as e:
ansible_module.fail_json(msg="%s %s" % (params, str(e)))
finally:
temp_kdestroy(ccache_dir, ccache_name)
# Done
ansible_module.exit_json(changed=changed, config=exit_args)
if __name__ == "__main__":
main()

View File

@@ -97,11 +97,10 @@ RETURN = """
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_text
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
temp_kdestroy, valid_creds, api_connect, api_command, \
temp_kdestroy, valid_creds, api_connect, \
api_command_no_name, compare_args_ipa, module_params_get, \
gen_add_del_lists, is_ipv4_addr, is_ipv6_addr, ipalib_errors
is_ipv4_addr, is_ipv6_addr
def find_dnsconfig(module):
@@ -116,7 +115,7 @@ def find_dnsconfig(module):
_result["result"]['idnsforwarders'] = ['']
return _result["result"]
else:
module.fail("Could not retrieve current DNS configuration.")
module.fail_json(msg="Could not retrieve current DNS configuration.")
return None
@@ -130,7 +129,7 @@ def gen_args(module, state, dnsconfig, forwarders, forward_policy,
ip_address = forwarder.get('ip_address')
port = forwarder.get('port')
if not (is_ipv4_addr(ip_address) or is_ipv6_addr(ip_address)):
module.fail(
module.fail_json(
msg="Invalid IP for DNS forwarder: %s" % ip_address)
if port is None:
_forwarders.append(ip_address)
@@ -154,7 +153,7 @@ def gen_args(module, state, dnsconfig, forwarders, forward_policy,
else:
# shouldn't happen, but let's be paranoid.
module.fail(msg="Invalid state: %s" % state)
module.fail_json(msg="Invalid state: %s" % state)
if forward_policy is not None:
_args['idnsforwardpolicy'] = forward_policy

File diff suppressed because it is too large Load Diff

View File

@@ -75,6 +75,18 @@ options:
- Only usable with IPA versions 4.7 and up.
required: false
type: list
membermanager_user:
description:
- List of member manager users assigned to this group.
- Only usable with IPA versions 4.8.4 and up.
required: false
type: list
membermanager_group:
description:
- List of member manager groups assigned to this group.
- Only usable with IPA versions 4.8.4 and up.
required: false
type: list
action:
description: Work on group or member level
default: group
@@ -141,7 +153,7 @@ RETURN = """
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
api_check_param, module_params_get
api_check_param, module_params_get, gen_add_del_lists, api_check_command
def find_group(module, name):
@@ -207,6 +219,9 @@ def main():
user=dict(required=False, type='list', default=None),
group=dict(required=False, type='list', default=None),
service=dict(required=False, type='list', default=None),
membermanager_user=dict(required=False, type='list', default=None),
membermanager_group=dict(required=False, type='list',
default=None),
action=dict(type="str", default="group",
choices=["member", "group"]),
# state
@@ -221,7 +236,10 @@ def main():
# Get parameters
# general
ipaadmin_principal = module_params_get(ansible_module, "ipaadmin_principal")
ipaadmin_principal = module_params_get(
ansible_module,
"ipaadmin_principal",
)
ipaadmin_password = module_params_get(ansible_module, "ipaadmin_password")
names = module_params_get(ansible_module, "name")
@@ -234,6 +252,10 @@ def main():
user = module_params_get(ansible_module, "user")
group = module_params_get(ansible_module, "group")
service = module_params_get(ansible_module, "service")
membermanager_user = module_params_get(ansible_module,
"membermanager_user")
membermanager_group = module_params_get(ansible_module,
"membermanager_group")
action = module_params_get(ansible_module, "action")
# state
state = module_params_get(ansible_module, "state")
@@ -284,6 +306,14 @@ def main():
msg="Managing a service as part of a group is not supported "
"by your IPA version")
has_add_membermanager = api_check_command("group_add_member_manager")
if ((membermanager_user is not None or
membermanager_group is not None) and not has_add_membermanager):
ansible_module.fail_json(
msg="Managing a membermanager user or group is not supported "
"by your IPA version"
)
commands = []
for name in names:
@@ -314,24 +344,14 @@ def main():
if not compare_args_ipa(ansible_module, member_args,
res_find):
# Generate addition and removal lists
user_add = list(
set(user or []) -
set(res_find.get("member_user", [])))
user_del = list(
set(res_find.get("member_user", [])) -
set(user or []))
group_add = list(
set(group or []) -
set(res_find.get("member_group", [])))
group_del = list(
set(res_find.get("member_group", [])) -
set(group or []))
service_add = list(
set(service or []) -
set(res_find.get("member_service", [])))
service_del = list(
set(res_find.get("member_service", [])) -
set(service or []))
user_add, user_del = gen_add_del_lists(
user, res_find.get("member_user"))
group_add, group_del = gen_add_del_lists(
group, res_find.get("member_group"))
service_add, service_del = gen_add_del_lists(
service, res_find.get("member_service"))
if has_add_member_service:
# Add members
@@ -367,6 +387,41 @@ def main():
"user": user_del,
"group": group_del,
}])
membermanager_user_add, membermanager_user_del = \
gen_add_del_lists(
membermanager_user,
res_find.get("membermanager_user")
)
membermanager_group_add, membermanager_group_del = \
gen_add_del_lists(
membermanager_group,
res_find.get("membermanager_group")
)
if has_add_membermanager:
# Add membermanager users and groups
if len(membermanager_user_add) > 0 or \
len(membermanager_group_add) > 0:
commands.append(
[name, "group_add_member_manager",
{
"user": membermanager_user_add,
"group": membermanager_group_add,
}]
)
# Remove member manager
if len(membermanager_user_del) > 0 or \
len(membermanager_group_del) > 0:
commands.append(
[name, "group_remove_member_manager",
{
"user": membermanager_user_del,
"group": membermanager_group_del,
}]
)
elif action == "member":
if res_find is None:
ansible_module.fail_json(msg="No group '%s'" % name)
@@ -384,6 +439,18 @@ def main():
"group": group,
}])
if has_add_membermanager:
# Add membermanager users and groups
if membermanager_user is not None or \
membermanager_group is not None:
commands.append(
[name, "group_add_member_manager",
{
"user": membermanager_user,
"group": membermanager_group,
}]
)
elif state == "absent":
if action == "group":
if res_find is not None:
@@ -393,12 +460,32 @@ def main():
if res_find is None:
ansible_module.fail_json(msg="No group '%s'" % name)
commands.append([name, "group_remove_member",
{
"user": user,
"group": group,
"service": service,
}])
if has_add_member_service:
commands.append([name, "group_remove_member",
{
"user": user,
"group": group,
"service": service,
}])
else:
commands.append([name, "group_remove_member",
{
"user": user,
"group": group,
}])
if has_add_membermanager:
# Remove membermanager users and groups
if membermanager_user is not None or \
membermanager_group is not None:
commands.append(
[name, "group_remove_member_manager",
{
"user": membermanager_user,
"group": membermanager_group,
}]
)
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)

View File

@@ -49,17 +49,17 @@ options:
description: User category the rule applies to
required: false
aliases: ["usercat"]
choices: ["all"]
choices: ["all", ""]
hostcategory:
description: Host category the rule applies to
required: false
aliases: ["hostcat"]
choices: ["all"]
choices: ["all", ""]
servicecategory:
description: Service category the rule applies to
required: false
aliases: ["servicecat"]
choices: ["all"]
choices: ["all", ""]
nomembers:
description: Suppress processing of membership attributes
required: false
@@ -159,7 +159,7 @@ RETURN = """
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
module_params_get
module_params_get, gen_add_del_lists
def find_hbacrule(module, name):
@@ -208,11 +208,11 @@ def main():
# present
description=dict(type="str", default=None),
usercategory=dict(type="str", default=None,
aliases=["usercat"], choices=["all"]),
aliases=["usercat"], choices=["all", ""]),
hostcategory=dict(type="str", default=None,
aliases=["hostcat"], choices=["all"]),
aliases=["hostcat"], choices=["all", ""]),
servicecategory=dict(type="str", default=None,
aliases=["servicecat"], choices=["all"]),
aliases=["servicecat"], choices=["all", ""]),
nomembers=dict(required=False, type='bool', default=None),
host=dict(required=False, type='list', default=None),
hostgroup=dict(required=False, type='list', default=None),
@@ -270,6 +270,16 @@ def main():
ansible_module.fail_json(
msg="Argument '%s' can not be used with action "
"'%s'" % (x, action))
else:
if hostcategory == 'all' and any([host, hostgroup]):
ansible_module.fail_json(
msg="Hosts cannot be added when host category='all'")
if usercategory == 'all' and any([user, group]):
ansible_module.fail_json(
msg="Users cannot be added when user category='all'")
if servicecategory == 'all' and any([hbacsvc, hbacsvcgroup]):
ansible_module.fail_json(
msg="Services cannot be added when service category='all'")
elif state == "absent":
if len(names) < 1:
@@ -342,44 +352,24 @@ def main():
res_find = {}
# Generate addition and removal lists
host_add = list(
set(host or []) -
set(res_find.get("memberhost_host", [])))
host_del = list(
set(res_find.get("memberhost_host", [])) -
set(host or []))
hostgroup_add = list(
set(hostgroup or []) -
set(res_find.get("memberhost_hostgroup", [])))
hostgroup_del = list(
set(res_find.get("memberhost_hostgroup", [])) -
set(hostgroup or []))
host_add, host_del = gen_add_del_lists(
host, res_find.get("memberhost_host"))
hbacsvc_add = list(
set(hbacsvc or []) -
set(res_find.get("memberservice_hbacsvc", [])))
hbacsvc_del = list(
set(res_find.get("memberservice_hbacsvc", [])) -
set(hbacsvc or []))
hbacsvcgroup_add = list(
set(hbacsvcgroup or []) -
set(res_find.get("memberservice_hbacsvcgroup", [])))
hbacsvcgroup_del = list(
set(res_find.get("memberservice_hbacsvcgroup", [])) -
set(hbacsvcgroup or []))
hostgroup_add, hostgroup_del = gen_add_del_lists(
hostgroup, res_find.get("memberhost_hostgroup"))
user_add = list(
set(user or []) -
set(res_find.get("memberuser_user", [])))
user_del = list(
set(res_find.get("memberuser_user", [])) -
set(user or []))
group_add = list(
set(group or []) -
set(res_find.get("memberuser_group", [])))
group_del = list(
set(res_find.get("memberuser_group", [])) -
set(group or []))
hbacsvc_add, hbacsvc_del = gen_add_del_lists(
hbacsvc, res_find.get("memberservice_hbacsvc"))
hbacsvcgroup_add, hbacsvcgroup_del = gen_add_del_lists(
hbacsvcgroup,
res_find.get("memberservice_hbacsvcgroup"))
user_add, user_del = gen_add_del_lists(
user, res_find.get("memberuser_user"))
group_add, group_del = gen_add_del_lists(
group, res_find.get("memberuser_group"))
# Add hosts and hostgroups
if len(host_add) > 0 or len(hostgroup_add) > 0:

View File

@@ -104,7 +104,8 @@ RETURN = """
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_text
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
gen_add_del_lists
def find_hbacsvcgroup(module, name):
@@ -249,12 +250,8 @@ def main():
if not compare_args_ipa(ansible_module, member_args,
res_find):
# Generate addition and removal lists
hbacsvc_add = list(
set(hbacsvc or []) -
set(res_find.get("member_hbacsvc", [])))
hbacsvc_del = list(
set(res_find.get("member_hbacsvc", [])) -
set(hbacsvc or []))
hbacsvc_add, hbacsvc_del = gen_add_del_lists(
hbacsvc, res_find.get("member_hbacsvc"))
# Add members
if len(hbacsvc_add) > 0:

View File

@@ -420,23 +420,22 @@ if six.PY3:
def find_host(module, name):
_args = {
"all": True,
"fqdn": to_text(name),
}
_result = api_command(module, "host_find", to_text(name), _args)
try:
_result = api_command(module, "host_show", to_text(name), _args)
except ipalib_errors.NotFound as e:
msg = str(e)
if "host not found" in msg:
return None
module.fail_json(msg="host_show failed: %s" % msg)
if len(_result["result"]) > 1:
module.fail_json(
msg="There is more than one host '%s'" % (name))
elif len(_result["result"]) == 1:
_res = _result["result"][0]
certs = _res.get("usercertificate")
if certs is not None:
_res["usercertificate"] = [encode_certificate(cert) for
cert in certs]
return _res
else:
return None
_res = _result["result"]
certs = _res.get("usercertificate")
if certs is not None:
_res["usercertificate"] = [encode_certificate(cert) for
cert in certs]
return _res
def find_dnsrecord(module, name):
@@ -445,24 +444,19 @@ def find_dnsrecord(module, name):
_args = {
"all": True,
"idnsname": to_text(host_name),
"idnsname": to_text(host_name)
}
_result = api_command(module, "dnsrecord_find", to_text(domain_name),
_args)
try:
_result = api_command(module, "dnsrecord_show", to_text(domain_name),
_args)
except ipalib_errors.NotFound as e:
msg = str(e)
if "record not found" in msg or "zone not found" in msg:
return None
module.fail_json(msg="dnsrecord_show failed: %s" % msg)
if len(_result["result"]) > 1:
module.fail_json(
msg="There is more than one host '%s'" % (name))
elif len(_result["result"]) == 1:
_res = _result["result"][0]
certs = _res.get("usercertificate")
if certs is not None:
_res["usercertificate"] = [encode_certificate(cert) for
cert in certs]
return _res
else:
return None
return _result["result"]
def show_host(module, name):
@@ -875,9 +869,11 @@ def main():
res_find_dnsrecord = find_dnsrecord(ansible_module, name)
except ipalib_errors.NotFound as e:
msg = str(e)
if ip_address is None and \
("DNS is not configured" in msg or \
"DNS zone not found" in msg):
dns_not_configured = "DNS is not configured" in msg
dns_zone_not_found = "DNS zone not found" in msg
if ip_address is None and (
dns_not_configured or dns_zone_not_found
):
# IP address(es) not given and no DNS support in IPA
# -> Ignore failure
# IP address(es) not given and DNS zone is not found
@@ -901,9 +897,25 @@ def main():
# Found the host
if res_find is not None:
# Ignore password with update_password == on_create
if update_password == "on_create" and \
"userpassword" in args:
del args["userpassword"]
if update_password == "on_create":
# Ignore userpassword and random for existing
# host if update_password is "on_create"
if "userpassword" in args:
del args["userpassword"]
if "random" in args:
del args["random"]
elif "userpassword" in args or "random" in args:
# Allow an existing OTP to be reset but don't
# allow a OTP or to be added to an enrolled host.
# Also do not allow to change the password for an
# enrolled host.
if not res_find["has_password"] and \
res_find["has_keytab"]:
ansible_module.fail_json(
msg="%s: Password cannot be set on "
"enrolled host." % host
)
# Ignore force, ip_address and no_reverse for mod
for x in ["force", "ip_address", "no_reverse"]:
@@ -951,7 +963,7 @@ def main():
principal_add, principal_del = gen_add_del_lists(
principal, res_find.get("principal"))
# Principals are not returned as utf8 for IPA using
# python2 using host_find, therefore we need to
# python2 using host_show, therefore we need to
# convert the principals that we should remove.
principal_del = [to_text(x) for x in principal_del]

View File

@@ -58,6 +58,18 @@ options:
description: List of hostgroup names assigned to this hostgroup.
required: false
type: list
membermanager_user:
description:
- List of member manager users assigned to this hostgroup.
- Only usable with IPA versions 4.8.4 and up.
required: false
type: list
membermanager_group:
description:
- List of member manager groups assigned to this hostgroup.
- Only usable with IPA versions 4.8.4 and up.
required: false
type: list
action:
description: Work on hostgroup or member level
default: hostgroup
@@ -117,7 +129,7 @@ RETURN = """
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
module_params_get
module_params_get, gen_add_del_lists, api_check_command
def find_hostgroup(module, name):
@@ -171,6 +183,9 @@ def main():
nomembers=dict(required=False, type='bool', default=None),
host=dict(required=False, type='list', default=None),
hostgroup=dict(required=False, type='list', default=None),
membermanager_user=dict(required=False, type='list', default=None),
membermanager_group=dict(required=False, type='list',
default=None),
action=dict(type="str", default="hostgroup",
choices=["member", "hostgroup"]),
# state
@@ -196,6 +211,10 @@ def main():
nomembers = module_params_get(ansible_module, "nomembers")
host = module_params_get(ansible_module, "host")
hostgroup = module_params_get(ansible_module, "hostgroup")
membermanager_user = module_params_get(ansible_module,
"membermanager_user")
membermanager_group = module_params_get(ansible_module,
"membermanager_group")
action = module_params_get(ansible_module, "action")
# state
state = module_params_get(ansible_module, "state")
@@ -239,6 +258,15 @@ def main():
ipaadmin_password)
api_connect()
has_add_membermanager = api_check_command(
"hostgroup_add_member_manager")
if ((membermanager_user is not None or
membermanager_group is not None) and not has_add_membermanager):
ansible_module.fail_json(
msg="Managing a membermanager user or group is not supported "
"by your IPA version"
)
commands = []
for name in names:
@@ -268,18 +296,11 @@ def main():
if not compare_args_ipa(ansible_module, member_args,
res_find):
# Generate addition and removal lists
host_add = list(
set(host or []) -
set(res_find.get("member_host", [])))
host_del = list(
set(res_find.get("member_host", [])) -
set(host or []))
hostgroup_add = list(
set(hostgroup or []) -
set(res_find.get("member_hostgroup", [])))
hostgroup_del = list(
set(res_find.get("member_hostgroup", [])) -
set(hostgroup or []))
host_add, host_del = gen_add_del_lists(
host, res_find.get("member_host"))
hostgroup_add, hostgroup_del = gen_add_del_lists(
hostgroup, res_find.get("member_hostgroup"))
# Add members
if len(host_add) > 0 or len(hostgroup_add) > 0:
@@ -295,6 +316,41 @@ def main():
"host": host_del,
"hostgroup": hostgroup_del,
}])
membermanager_user_add, membermanager_user_del = \
gen_add_del_lists(
membermanager_user,
res_find.get("membermanager_user")
)
membermanager_group_add, membermanager_group_del = \
gen_add_del_lists(
membermanager_group,
res_find.get("membermanager_group")
)
if has_add_membermanager:
# Add membermanager users and groups
if len(membermanager_user_add) > 0 or \
len(membermanager_group_add) > 0:
commands.append(
[name, "hostgroup_add_member_manager",
{
"user": membermanager_user_add,
"group": membermanager_group_add,
}]
)
# Remove member manager
if len(membermanager_user_del) > 0 or \
len(membermanager_group_del) > 0:
commands.append(
[name, "hostgroup_remove_member_manager",
{
"user": membermanager_user_del,
"group": membermanager_group_del,
}]
)
elif action == "member":
if res_find is None:
ansible_module.fail_json(
@@ -306,6 +362,19 @@ def main():
"host": host,
"hostgroup": hostgroup,
}])
if has_add_membermanager:
# Add membermanager users and groups
if membermanager_user is not None or \
membermanager_group is not None:
commands.append(
[name, "hostgroup_add_member_manager",
{
"user": membermanager_user,
"group": membermanager_group,
}]
)
elif state == "absent":
if action == "hostgroup":
if res_find is not None:
@@ -322,6 +391,19 @@ def main():
"host": host,
"hostgroup": hostgroup,
}])
if has_add_membermanager:
# Remove membermanager users and groups
if membermanager_user is not None or \
membermanager_group is not None:
commands.append(
[name, "hostgroup_remove_member_manager",
{
"user": membermanager_user,
"group": membermanager_group,
}]
)
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)

View File

@@ -90,6 +90,14 @@ options:
required: false
type: list
aliases: ["krbprincipalname"]
smb:
description: Add a SMB service. Can only be used with new services.
required: false
type: bool
netbiosname:
description: NETBIOS name for the SMB service.
required: false
type: str
host:
description: Host that can manage the service.
required: false
@@ -135,6 +143,12 @@ options:
required: false
type: list
aliases: ["ipaallowedtoperform_read_keys_hostgroup"]
continue:
description:
Continuous mode. Don't stop on errors. Valid only if `state` is `absent`.
required: false
default: True
type: bool
action:
description: Work on service or member level
default: service
@@ -142,7 +156,7 @@ options:
state:
description: State to ensure
default: present
choices: ["present", "absent", "enabled", "disabled"]
choices: ["present", "absent", "disabled"]
author:
- Rafael Jeffman
"""
@@ -217,20 +231,31 @@ from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
encode_certificate, gen_add_del_lists, module_params_get, to_text, \
api_check_param
import ipalib.errors
def find_service(module, name):
def find_service(module, name, netbiosname):
_args = {
"all": True,
}
_result = api_command(module, "service_find", to_text(name), _args)
# Search for a SMB/cifs service.
if netbiosname is not None:
_result = api_command(
module, "service_find", to_text(netbiosname), _args)
if len(_result["result"]) > 1:
module.fail_json(
msg="There is more than one service '%s'" % (name))
elif len(_result["result"]) == 1:
_res = _result["result"][0]
for _res_find in _result.get('result', []):
for uid in _res_find.get('uid', []):
if uid.startswith("%s$@" % netbiosname):
return _res_find
try:
_result = api_command(module, "service_show", to_text(name), _args)
except ipalib.errors.NotFound:
return None
if "result" in _result:
_res = _result["result"]
certs = _res.get("usercertificate")
if certs is not None:
_res["usercertificate"] = [encode_certificate(cert) for
@@ -268,7 +293,7 @@ def check_parameters(module, state, action, names, parameters):
# invalid parameters for everything but state 'present', action 'service'.
invalid = ['pac_type', 'auth_ind', 'skip_host_check',
'force', 'requires_pre_auth', 'ok_as_delegate',
'ok_to_auth_as_delegate']
'ok_to_auth_as_delegate', 'smb', 'netbiosname']
# invalid parameters when not handling service members.
invalid_not_member = \
@@ -283,7 +308,19 @@ def check_parameters(module, state, action, names, parameters):
module.fail_json(msg="Only one service can be added at a time.")
if action == 'service':
invalid = []
invalid = ['delete_continue']
if parameters.get('smb', False):
invalid.extend(['force', 'auth_ind', 'skip_host_check',
'requires_pre_auth', 'auth_ind', 'pac_type'])
for _invalid in invalid:
if parameters.get(_invalid, False):
module.fail_json(
msg="Argument '%s' can not be used with SMB "
"service." % _invalid)
else:
invalid.append('delete_continue')
elif state == 'absent':
if len(names) < 1:
@@ -291,9 +328,12 @@ def check_parameters(module, state, action, names, parameters):
if action == "service":
invalid.extend(invalid_not_member)
else:
invalid.extend('delete_continue')
elif state == 'disabled':
invalid.extend(invalid_not_member)
invalid.append('delete_continue')
if action != "service":
module.fail_json(
msg="Invalid action '%s' for state '%s'" % (action, state))
@@ -302,10 +342,10 @@ def check_parameters(module, state, action, names, parameters):
module.fail_json(msg="Invalid state '%s'" % (state))
for _invalid in invalid:
if parameters[_invalid] is not None:
if _invalid in parameters and parameters[_invalid] is not None:
module.fail_json(
msg="Argument '%s' can not be used with state '%s'" %
(_invalid, state))
msg="Argument '%s' can not be used with state '%s', "
"action '%s'" % (_invalid, state, action))
def init_ansible_module():
@@ -322,11 +362,13 @@ def init_ansible_module():
default=None, required=False),
principal=dict(type="list", aliases=["krbprincipalname"],
default=None),
smb=dict(type="bool", required=False),
netbiosname=dict(type="str", required=False),
pac_type=dict(type="list", aliases=["ipakrbauthzdata"],
choices=["MS-PAC", "PAD", "NONE"]),
auth_ind=dict(type="str",
auth_ind=dict(type="list",
aliases=["krbprincipalauthind"],
choices=["otp", "radius", "pkinit", "hardened"]),
choices=["otp", "radius", "pkinit", "hardened", ""]),
skip_host_check=dict(type="bool"),
force=dict(type="bool"),
requires_pre_auth=dict(
@@ -359,13 +401,14 @@ def init_ansible_module():
allow_retrieve_keytab_hostgroup=dict(
type="list", required=False,
aliases=['ipaallowedtoperform_read_keys_hostgroup']),
delete_continue=dict(type="bool", required=False,
aliases=['continue']),
# action
action=dict(type="str", default="service",
choices=["member", "service"]),
# state
state=dict(type="str", default="present",
choices=["present", "absent",
"enabled", "disabled"]),
choices=["present", "absent", "disabled"]),
),
supports_check_mode=True,
)
@@ -398,6 +441,9 @@ def main():
ok_to_auth_as_delegate = module_params_get(ansible_module,
"ok_to_auth_as_delegate")
smb = module_params_get(ansible_module, "smb")
netbiosname = module_params_get(ansible_module, "netbiosname")
host = module_params_get(ansible_module, "host")
allow_create_keytab_user = module_params_get(
@@ -417,6 +463,7 @@ def main():
ansible_module, "allow_create_keytab_host")
allow_retrieve_keytab_hostgroup = module_params_get(
ansible_module, "allow_retrieve_keytab_hostgroup")
delete_continue = module_params_get(ansible_module, "delete_continue")
# action
action = module_params_get(ansible_module, "action")
@@ -447,9 +494,11 @@ def main():
commands = []
for name in names:
res_find = find_service(ansible_module, name)
res_find = find_service(ansible_module, name, netbiosname)
if state == "present":
# if service exists, 'smb' cannot be used.
if action == "service":
args = gen_args(
pac_type, auth_ind, skip_host_check, force,
@@ -459,7 +508,12 @@ def main():
del args['skip_host_check']
if res_find is None:
commands.append([name, 'service_add', args])
if smb:
if netbiosname is not None:
args['ipantflatname'] = netbiosname
commands.append([name, 'service_add_smb', args])
else:
commands.append([name, 'service_add', args])
certificate_add = certificate or []
certificate_del = []
@@ -699,7 +753,8 @@ def main():
elif state == "absent":
if action == "service":
if res_find is not None:
commands.append([name, 'service_del', {}])
args = {'continue': True if delete_continue else False}
commands.append([name, 'service_del', args])
elif action == "member":
if res_find is None:

View File

@@ -110,7 +110,8 @@ RETURN = """
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_text
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
gen_add_del_lists
def find_sudocmdgroup(module, name):
@@ -257,12 +258,10 @@ def main():
if not compare_args_ipa(ansible_module, member_args,
res_find):
# Generate addition and removal lists
sudocmdgroup_add = list(
set(sudocmdgroup or []) -
set(res_find.get("member_sudocmdgroup", [])))
sudocmdgroup_del = list(
set(res_find.get("member_sudocmdgroup", [])) -
set(sudocmdgroup or []))
sudocmdgroup_add, sudocmdgroup_del = \
gen_add_del_lists(
sudocmdgroup,
res_find.get("member_sudocmdgroup"))
# Add members
if len(sudocmdgroup_add) > 0:

View File

@@ -51,18 +51,21 @@ options:
usercategory:
description: User category the sudo rule applies to
required: false
choices: ["all"]
choices: ["all", ""]
aliases: ["usercat"]
usergroup:
description: List of user groups assigned to the sudo rule.
required: false
runasgroupcategory:
description: RunAs Group category applied to the sudo rule.
required: false
choices: ["all"]
choices: ["all", ""]
aliases: ["runasgroupcat"]
runasusercategory:
description: RunAs User category applied to the sudorule.
required: false
choices: ["all"]
choices: ["all", ""]
aliases: ["runasusercat"]
nomembers:
description: Suppress processing of membership attributes
required: false
@@ -78,7 +81,8 @@ options:
hostcategory:
description: Host category the sudo rule applies to.
required: false
choices: ["all"]
choices: ["all", ""]
aliases: ["hostcat"]
allow_sudocmd:
description: List of allowed sudocmds assigned to this sudorule.
required: false
@@ -98,7 +102,8 @@ options:
cmdcategory:
description: Command category the sudo rule applies to
required: false
choices: ["all"]
choices: ["all", ""]
aliases: ["cmdcat"]
order:
description: Order to apply this rule.
required: false
@@ -241,9 +246,9 @@ def main():
# present
description=dict(required=False, type="str", default=None),
usercategory=dict(required=False, type="str", default=None,
choices=["all"]),
choices=["all", ""], aliases=['usercat']),
hostcategory=dict(required=False, type="str", default=None,
choices=["all"]),
choices=["all", ""], aliases=['hostcat']),
nomembers=dict(required=False, type='bool', default=None),
host=dict(required=False, type='list', default=None),
hostgroup=dict(required=False, type='list', default=None),
@@ -254,11 +259,13 @@ def main():
allow_sudocmdgroup=dict(required=False, type="list", default=None),
deny_sudocmdgroup=dict(required=False, type="list", default=None),
cmdcategory=dict(required=False, type="str", default=None,
choices=["all"]),
choices=["all", ""], aliases=['cmdcat']),
runasusercategory=dict(required=False, type="str", default=None,
choices=["all"]),
choices=["all", ""],
aliases=['runasusercat']),
runasgroupcategory=dict(required=False, type="str", default=None,
choices=["all"]),
choices=["all", ""],
aliases=['runasgroupcat']),
runasuser=dict(required=False, type="list", default=None),
runasgroup=dict(required=False, type="list", default=None),
order=dict(type="int", required=False, aliases=['sudoorder']),
@@ -332,6 +339,17 @@ def main():
ansible_module.fail_json(
msg="Argument '%s' can not be used with action "
"'%s'" % (arg, action))
else:
if hostcategory == 'all' and any([host, hostgroup]):
ansible_module.fail_json(
msg="Hosts cannot be added when host category='all'")
if usercategory == 'all' and any([user, group]):
ansible_module.fail_json(
msg="Users cannot be added when user category='all'")
if cmdcategory == 'all' \
and any([allow_sudocmd, allow_sudocmdgroup]):
ansible_module.fail_json(
msg="Commands cannot be added when command category='all'")
elif state == "absent":
if len(names) < 1:

View File

@@ -186,7 +186,9 @@ options:
description: List of base-64 encoded user certificates
required: false
certmapdata:
description: List of certificate mappings
description:
- List of certificate mappings
- Only usable with IPA versions 4.5 and up.
options:
certificate:
description: Base-64 encoded user certificate
@@ -197,6 +199,9 @@ options:
subject:
description: Subject of the certificate
required: false
data:
description: Certmap data
required: false
required: false
noprivate:
description: Don't create user private group
@@ -346,7 +351,9 @@ options:
description: List of base-64 encoded user certificates
required: false
certmapdata:
description: List of certificate mappings
description:
- List of certificate mappings
- Only usable with IPA versions 4.5 and up.
options:
certificate:
description: Base-64 encoded user certificate
@@ -357,6 +364,9 @@ options:
subject:
description: Subject of the certificate
required: false
data:
description: Certmap data
required: false
required: false
noprivate:
description: Don't create user private group
@@ -467,7 +477,8 @@ from ansible.module_utils._text import to_text
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
temp_kdestroy, valid_creds, api_connect, api_command, date_format, \
compare_args_ipa, module_params_get, api_check_param, api_get_realm, \
api_command_no_name
api_command_no_name, gen_add_del_lists, encode_certificate, \
load_cert_from_str, DN_x500_text, api_check_command
import six
@@ -497,6 +508,11 @@ def find_user(module, name, preserved=False):
for x in _result["krbprincipalname"]:
_list.append(str(x))
_result["krbprincipalname"] = _list
certs = _result.get("usercertificate")
if certs is not None:
_result["usercertificate"] = [encode_certificate(x)
for x in certs]
return _result
else:
return None
@@ -640,13 +656,21 @@ def check_parameters(module, state, action,
certificate = x.get("certificate")
issuer = x.get("issuer")
subject = x.get("subject")
data = x.get("data")
if data is not None:
if certificate is not None or issuer is not None or \
subject is not None:
module.fail_json(
msg="certmapdata: data can not be used with "
"certificate, issuer or subject")
check_certmapdata(data)
if certificate is not None \
and (issuer is not None or subject is not None):
module.fail_json(
msg="certmapdata: certificate can not be used with "
"issuer or subject")
if certificate is None:
if data is None and certificate is None:
if issuer is None:
module.fail_json(msg="certmapdata: issuer is missing")
if subject is None:
@@ -655,25 +679,54 @@ def check_parameters(module, state, action,
def extend_emails(email, default_email_domain):
if email is not None:
return [ "%s@%s" % (_email, default_email_domain)
if "@" not in _email else _email
for _email in email]
return ["%s@%s" % (_email, default_email_domain)
if "@" not in _email else _email
for _email in email]
return email
def gen_certmapdata_args(certmapdata):
certificate = certmapdata.get("certificate")
issuer = certmapdata.get("issuer")
subject = certmapdata.get("subject")
def convert_certmapdata(certmapdata):
if certmapdata is None:
return None
_args = {}
if certificate is not None:
_args["certificate"] = certificate
if issuer is not None:
_args["issuer"] = issuer
if subject is not None:
_args["subject"] = subject
return _args
_result = []
for x in certmapdata:
certificate = x.get("certificate")
issuer = x.get("issuer")
subject = x.get("subject")
data = x.get("data")
if data is None:
if issuer is None and subject is None:
cert = load_cert_from_str(certificate)
issuer = cert.issuer
subject = cert.subject
_result.append("X509:<I>%s<S>%s" % (DN_x500_text(issuer),
DN_x500_text(subject)))
else:
_result.append(data)
return _result
def check_certmapdata(data):
if not data.startswith("X509:"):
return False
i = data.find("<I>", 4)
s = data.find("<S>", i)
issuer = data[i+3:s]
subject = data[s+3:]
if i < 0 or s < 0 or "CN" not in issuer or "CN" not in subject:
return False
return True
def gen_certmapdata_args(certmapdata):
return {"ipacertmapdata": to_text(certmapdata)}
def main():
@@ -735,7 +788,8 @@ def main():
# Here certificate is a simple string
certificate=dict(type="str", default=None),
issuer=dict(type="str", default=None),
subject=dict(type="str", default=None)
subject=dict(type="str", default=None),
data=dict(type="str", default=None)
),
elements='dict', required=False),
noprivate=dict(type='bool', default=None),
@@ -763,7 +817,7 @@ def main():
preserve=dict(required=False, type='bool', default=None),
# mod
update_password=dict(type='str', default=None,
update_password=dict(type='str', default=None, no_log=False,
choices=['always', 'on_create']),
# general
@@ -870,6 +924,7 @@ def main():
departmentnumber, employeenumber, employeetype, preferredlanguage,
certificate, certmapdata, noprivate, nomembers, preserve,
update_password)
certmapdata = convert_certmapdata(certmapdata)
# Use users if names is None
if users is not None:
@@ -967,6 +1022,7 @@ def main():
employeetype, preferredlanguage, certificate,
certmapdata, noprivate, nomembers, preserve,
update_password)
certmapdata = convert_certmapdata(certmapdata)
# Extend email addresses
@@ -996,6 +1052,16 @@ def main():
msg="The use of passwordexpiration is not supported by "
"your IPA version")
# Check certmapdata availability.
# We need the connected API for this test, therefore it can not
# be part of check_parameters as this is used also before the
# connection to the API has been established.
if certmapdata is not None and \
not api_check_command("user_add_certmapdata"):
ansible_module.fail_json(
msg="The use of certmapdata is not supported by "
"your IPA version")
# Make sure user exists
res_find = find_user(ansible_module, name)
# Also search for preserved user if the user could not be found
@@ -1063,36 +1129,21 @@ def main():
# certmapdata
if res_find is not None:
# Generate addition and removal lists
manager_add = list(
set(manager or []) -
set(res_find.get("manager", [])))
manager_del = list(
set(res_find.get("manager", [])) -
set(manager or []))
principal_add = list(
set(principal or []) -
set(res_find.get("krbprincipalname", [])))
principal_del = list(
set(res_find.get("krbprincipalname", [])) -
set(principal or []))
manager_add, manager_del = gen_add_del_lists(
manager, res_find.get("manager"))
principal_add, principal_del = gen_add_del_lists(
principal, res_find.get("krbprincipalname"))
# Principals are not returned as utf8 for IPA using
# python2 using user_find, therefore we need to
# convert the principals that we should remove.
principal_del = [to_text(x) for x in principal_del]
certificate_add = list(
set(certificate or []) -
set(res_find.get("certificate", [])))
certificate_del = list(
set(res_find.get("certificate", [])) -
set(certificate or []))
certmapdata_add = list(
set(certmapdata or []) -
set(res_find.get("ipaCertMapData", [])))
certmapdata_del = list(
set(res_find.get("ipaCertMapData", [])) -
set(certmapdata or []))
certificate_add, certificate_del = gen_add_del_lists(
certificate, res_find.get("usercertificate"))
certmapdata_add, certmapdata_del = gen_add_del_lists(
certmapdata, res_find.get("ipacertmapdata"))
else:
# Use given managers and principals
@@ -1179,7 +1230,7 @@ def main():
# Remove certmapdata
if len(certmapdata_del) > 0:
for _data in certmapdata_del:
commands.append([name, "user_add_certmapdata",
commands.append([name, "user_remove_certmapdata",
gen_certmapdata_args(_data)])
elif action == "member":
@@ -1376,7 +1427,6 @@ def main():
temp_kdestroy(ccache_dir, ccache_name)
# Done
ansible_module.exit_json(changed=changed, user=exit_args)

View File

@@ -45,21 +45,41 @@ options:
description:
description: The vault description
required: false
vault_public_key:
description: Base64 encoded public key.
public_key:
description: Base64 encode public key.
required: false
type: list
aliases: ["ipavaultpublickey"]
vault_salt:
description: Vault salt.
type: string
aliases: ["ipavaultpublickey", "vault_public_key"]
public_key_file:
description: Path to file with public key.
required: false
type: list
aliases: ["ipavaultsalt"]
vault_password:
type: string
aliases: ["vault_public_key_file"]
private_key:
description: Base64 encode private key.
required: false
type: string
aliases: ["ipavaultprivatekey", "vault_private_key"]
private_key_file:
description: Path to file with private key.
required: false
type: string
aliases: ["vault_private_key_file"]
password:
description: password to be used on symmetric vault.
required: false
type: string
aliases: ["ipavaultpassword"]
aliases: ["ipavaultpassword", "vault_password"]
password_file:
description: file with password to be used on symmetric vault.
required: false
type: string
aliases: ["vault_password_file"]
salt:
description: Vault salt.
required: false
type: list
aliases: ["ipavaultsalt", "vault_salt"]
vault_type:
description: Vault types are based on security level.
required: true
@@ -79,23 +99,45 @@ options:
description: Vault is shared.
required: false
type: boolean
vault_data:
description: Data to be stored in the vault.
required: false
type: string
aliases: ["ipavaultdata"]
owners:
description: Users that are owners of the container.
required: false
type: list
users:
description: Users that are member of the container.
description: Users that are member of the vault.
required: false
type: list
groups:
description: Groups that are member of the container.
description: Groups that are member of the vault.
required: false
type: list
owners:
description: Users that are owners of the vault.
required: false
type: list
ownergroups:
description: Groups that are owners of the vault.
required: false
type: list
ownerservices:
description: Services that are owners of the vault.
required: false
type: list
services:
description: Services that are member of the container.
required: false
type: list
data:
description: Data to be stored in the vault.
required: false
type: string
aliases: ["ipavaultdata", "vault_data"]
in:
description: Path to file with data to be stored in the vault.
required: false
type: string
aliases: ["datafile_in"]
out:
description: Path to file to store data retrieved from the vault.
required: false
type: string
aliases: ["datafile_out"]
action:
description: Work on vault or member level.
default: vault
@@ -103,7 +145,7 @@ options:
state:
description: State to ensure
default: present
choices: ["present", "absent"]
choices: ["present", "absent", "retrieved"]
author:
- Rafael Jeffman
"""
@@ -114,9 +156,9 @@ EXAMPLES = """
ipaadmin_password: SomeADMINpassword
name: symvault
username: admin
vault_password: MyVaultPassword123
vault_salt: MTIzNDU2Nzg5MAo=
vault_type: symmetric
password: SomeVAULTpassword
salt: MTIzNDU2Nzg5MAo=
# Ensure group ipausers is a vault member.
- ipavault:
@@ -178,12 +220,23 @@ EXAMPLES = """
ipaadmin_password: SomeADMINpassword
name: symvault
username: admin
vault_password: MyVaultPassword123
vault_data: >
password: SomeVAULTpassword
data: >
Data archived.
More data archived.
action: member
# Retrieve data archived from a symmetric vault
- ipavault:
ipaadmin_password: SomeADMINpassword
name: symvault
username: admin
password: SomeVAULTpassword
state: retrieved
register: result
- debug:
msg: "{{ result.data | b64decode }}"
# Ensure vault symvault is absent
- ipavault:
ipaadmin_password: SomeADMINpassword
@@ -198,7 +251,7 @@ EXAMPLES = """
username: user01
description: An asymmetric vault
vault_type: asymmetric
vault_public_key:
public_key:
LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTR
HTkFEQ0JpUUtCZ1FDdGFudjRkK3ptSTZ0T3ova1RXdGowY3AxRAowUENoYy8vR0pJMTUzTi
9CN3UrN0h3SXlRVlZoNUlXZG1UcCtkWXYzd09yeVpPbzYvbHN5eFJaZ2pZRDRwQ3VGCjlxM
@@ -211,11 +264,19 @@ EXAMPLES = """
ipaadmin_password: SomeADMINpassword
name: asymvault
username: admin
vault_data: >
data: >
Data archived.
More data archived.
action: member
# Retrive data archived in an asymmetric vault, using a private key file.
- ipavault:
ipaadmin_password: SomeADMINpassword
name: asymvault
username: admin
private_key_file: private.pem
state: retrieved
# Ensure asymmetric vault is absent.
- ipavault:
ipaadmin_password: SomeADMINpassword
@@ -226,9 +287,14 @@ EXAMPLES = """
"""
RETURN = """
user:
description: The vault data.
returned: If state is retrieved.
type: string
"""
import os
from base64 import b64decode
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
temp_kdestroy, valid_creds, api_connect, api_command, \
@@ -261,7 +327,8 @@ def find_vault(module, name, username, service, shared):
def gen_args(description, username, service, shared, vault_type, salt,
public_key, vault_data):
password, password_file, public_key, public_key_file, vault_data,
datafile_in, datafile_out):
_args = {}
if description is not None:
@@ -277,14 +344,16 @@ def gen_args(description, username, service, shared, vault_type, salt,
if salt is not None:
_args['ipavaultsalt'] = salt
if public_key is not None:
_args['ipavaultpublickey'] = public_key
if vault_data is not None:
_args['data'] = vault_data.encode('utf-8')
_args['ipavaultpublickey'] = b64decode(public_key.encode('utf-8'))
if public_key_file is not None:
with open(public_key_file, 'r') as keyfile:
keydata = keyfile.read()
_args['ipavaultpublickey'] = keydata.strip().encode('utf-8')
return _args
def gen_member_args(args, users, groups):
def gen_member_args(args, users, groups, services):
_args = args.copy()
for arg in ['ipavaulttype', 'description', 'ipavaultpublickey',
@@ -292,13 +361,21 @@ def gen_member_args(args, users, groups):
if arg in _args:
del _args[arg]
_args['user'] = users
_args['group'] = groups
if any([users, groups, services]):
if users is not None:
_args['user'] = users
if groups is not None:
_args['group'] = groups
if services is not None:
_args['services'] = services
return _args
return _args
return None
def data_storage_args(args, data, password):
def data_storage_args(args, data, password, password_file, private_key,
private_key_file, datafile_in, datafile_out):
_args = {}
if 'username' in args:
@@ -310,53 +387,104 @@ def data_storage_args(args, data, password):
if password is not None:
_args['password'] = password
if password_file is not None:
_args['password_file'] = password_file
_args['data'] = data
if private_key is not None:
_args['private_key'] = private_key
if private_key_file is not None:
_args['private_key_file'] = private_key_file
if datafile_in is not None:
_args['in'] = datafile_in
else:
if data is None:
_args['data'] = b''
else:
_args['data'] = data.encode('utf-8')
if datafile_out is not None:
_args['out'] = datafile_out
if private_key_file is not None:
_args['private_key_file'] = private_key_file
return _args
def check_parameters(module, state, action, description, username, service,
shared, users, groups, owners, ownergroups, vault_type,
salt, password, public_key, vault_data):
shared, users, groups, services, owners, ownergroups,
ownerservices, vault_type, salt, password, password_file,
public_key, public_key_file, private_key,
private_key_file, vault_data, datafile_in, datafile_out):
invalid = []
if state == "present":
if action == "member":
invalid = ['description', 'public_key', 'salt']
invalid = ['private_key', 'private_key_file', 'datafile_out']
for param in invalid:
if vars()[param] is not None:
module.fail_json(
msg="Argument '%s' can not be used with action '%s'" %
(param, action))
if action == "member":
invalid.extend(['description'])
elif state == "absent":
invalid = ['description', 'salt']
invalid = ['description', 'salt', 'vault_type', 'private_key',
'private_key_file', 'datafile_in', 'datafile_out',
'vault_data']
if action == "vault":
invalid.extend(['users', 'groups', 'owners', 'ownergroups',
'password', 'public_key'])
invalid.extend(['users', 'groups', 'services', 'owners',
'ownergroups', 'ownerservices', 'password',
'password_file', 'public_key', 'public_key_file'])
for arg in invalid:
if vars()[arg] is not None:
module.fail_json(
msg="Argument '%s' can not be used with action '%s'" %
(arg, state))
elif state == "retrieved":
invalid = ['description', 'salt', 'datafile_in', 'users', 'groups',
'owners', 'ownergroups', 'public_key', 'public_key_file',
'vault_data']
if action == 'member':
module.fail_json(
msg="State `retrieved` do not support action `member`.")
for arg in invalid:
if vars()[arg] is not None:
module.fail_json(
msg="Argument '%s' can not be used with state '%s', "
"action '%s'" % (arg, state, action))
for arg in invalid:
if vars()[arg] is not None:
module.fail_json(
msg="Argument '%s' can not be used with state '%s', "
"action '%s'" % (arg, state, action))
def check_encryption_params(module, state, vault_type, password, public_key,
vault_data, res_find):
if state == "present":
if vault_type == "symmetric":
if password is None \
and (vault_data is not None or res_find is None):
module.fail_json(
msg="Vault password required for symmetric vault.")
def check_encryption_params(module, state, action, vault_type, salt,
password, password_file, public_key,
public_key_file, private_key, private_key_file,
vault_data, datafile_in, datafile_out, res_find):
vault_type_invalid = []
if vault_type == "standard":
vault_type_invalid = ['public_key', 'public_key_file', 'password',
'password_file', 'salt']
if vault_type == "asymmetric":
if public_key is None and res_find is None:
module.fail_json(
msg="Public Key required for asymmetric vault.")
if vault_type is None or vault_type == "symmetric":
vault_type_invalid = ['public_key', 'public_key_file',
'private_key', 'private_key_file']
if password is None and password_file is None and action != 'member':
module.fail_json(
msg="Symmetric vault requires password or password_file "
"to store data or change `salt`.")
if vault_type == "asymmetric":
vault_type_invalid = ['password', 'password_file']
if not any([public_key, public_key_file]) and res_find is None:
module.fail_json(
msg="Assymmetric vault requires public_key "
"or public_key_file to store data.")
for param in vault_type_invalid:
if vars()[param] is not None:
module.fail_json(
msg="Argument '%s' cannot be used with vault type '%s'" %
(param, vault_type or 'symmetric'))
def main():
@@ -369,16 +497,23 @@ def main():
name=dict(type="list", aliases=["cn"], default=None,
required=True),
# present
description=dict(required=False, type="str", default=None),
vault_type=dict(type="str", aliases=["ipavaulttype"],
default=None, required=False,
choices=["standard", "symmetric", "asymmetric"]),
vault_public_key=dict(type="str", required=False, default=None,
aliases=['ipavaultpublickey']),
aliases=['ipavaultpublickey', 'public_key']),
vault_public_key_file=dict(type="str", required=False,
default=None,
aliases=['public_key_file']),
vault_private_key=dict(
type="str", required=False, default=None, no_log=True,
aliases=['ipavaultprivatekey', 'private_key']),
vault_private_key_file=dict(type="str", required=False,
default=None,
aliases=['private_key_file']),
vault_salt=dict(type="str", required=False, default=None,
aliases=['ipavaultsalt']),
aliases=['ipavaultsalt', 'salt']),
username=dict(type="str", required=False, default=None,
aliases=['user']),
service=dict(type="str", required=False, default=None),
@@ -386,23 +521,33 @@ def main():
users=dict(required=False, type='list', default=None),
groups=dict(required=False, type='list', default=None),
owners=dict(required=False, type='list', default=None),
services=dict(required=False, type='list', default=None),
owners=dict(required=False, type='list', default=None,
aliases=['ownerusers']),
ownergroups=dict(required=False, type='list', default=None),
ownerservices=dict(required=False, type='list', default=None),
vault_data=dict(type="str", required=False, default=None,
aliases=['ipavaultdata']),
no_log=True, aliases=['ipavaultdata', 'data']),
datafile_in=dict(type="str", required=False, default=None,
aliases=['in']),
datafile_out=dict(type="str", required=False, default=None,
aliases=['out']),
vault_password=dict(type="str", required=False, default=None,
no_log=True, aliases=['ipavaultpassword']),
aliases=['ipavaultpassword', 'password'],
no_log=True),
vault_password_file=dict(type="str", required=False, default=None,
no_log=False, aliases=['password_file']),
# state
action=dict(type="str", default="vault",
choices=["vault", "data", "member"]),
state=dict(type="str", default="present",
choices=["present", "absent"]),
choices=["present", "absent", "retrieved"]),
),
supports_check_mode=True,
mutually_exclusive=[['username', 'service', 'shared']],
required_one_of=[['username', 'service', 'shared']]
mutually_exclusive=[['username', 'service', 'shared'],
['datafile_in', 'vault_data'],
['vault_password', 'vault_password_file'],
['vault_public_key', 'vault_public_key_file']],
)
ansible_module._ansible_debug = True
@@ -422,18 +567,28 @@ def main():
users = module_params_get(ansible_module, "users")
groups = module_params_get(ansible_module, "groups")
services = module_params_get(ansible_module, "services")
owners = module_params_get(ansible_module, "owners")
ownergroups = module_params_get(ansible_module, "ownergroups")
ownerservices = module_params_get(ansible_module, "ownerservices")
vault_type = module_params_get(ansible_module, "vault_type")
salt = module_params_get(ansible_module, "vault_salt")
password = module_params_get(ansible_module, "vault_password")
password_file = module_params_get(ansible_module, "vault_password_file")
public_key = module_params_get(ansible_module, "vault_public_key")
public_key_file = module_params_get(ansible_module,
"vault_public_key_file")
private_key = module_params_get(ansible_module, "vault_private_key")
private_key_file = module_params_get(ansible_module,
"vault_private_key_file")
vault_data = module_params_get(ansible_module, "vault_data")
datafile_in = module_params_get(ansible_module, "datafile_in")
datafile_out = module_params_get(ansible_module, "datafile_out")
action = module_params_get(ansible_module, "action")
# state
state = module_params_get(ansible_module, "state")
# Check parameters
@@ -447,12 +602,19 @@ def main():
if len(names) < 1:
ansible_module.fail_json(msg="No name given.")
elif state == "retrieved":
if len(names) != 1:
ansible_module.fail_json(
msg="Only one vault can be retrieved at a time.")
else:
ansible_module.fail_json(msg="Invalid state '%s'" % state)
check_parameters(ansible_module, state, action, description, username,
service, shared, users, groups, owners, ownergroups,
vault_type, salt, password, public_key, vault_data)
service, shared, users, groups, services, owners,
ownergroups, ownerservices, vault_type, salt, password,
password_file, public_key, public_key_file, private_key,
private_key_file, vault_data, datafile_in, datafile_out)
# Init
changed = False
@@ -463,6 +625,9 @@ def main():
if not valid_creds(ansible_module, ipaadmin_principal):
ccache_dir, ccache_name = temp_kinit(ipaadmin_principal,
ipaadmin_password)
# Need to set krb5 ccache name, due to context='ansible-freeipa'
if ccache_name is not None:
os.environ["KRB5CCNAME"] = ccache_name
api_connect(context='ansible-freeipa')
@@ -475,7 +640,10 @@ def main():
# Generate args
args = gen_args(description, username, service, shared, vault_type,
salt, public_key, vault_data)
salt, password, password_file, public_key,
public_key_file, vault_data, datafile_in,
datafile_out)
pwdargs = None
# Set default vault_type if needed.
if vault_type is None and vault_data is not None:
@@ -485,12 +653,14 @@ def main():
else:
args['ipavaulttype'] = vault_type = "symmetric"
# verify data encription args
check_encryption_params(ansible_module, state, vault_type,
password, public_key, vault_data, res_find)
# Create command
if state == "present":
# verify data encription args
check_encryption_params(
ansible_module, state, action, vault_type, salt, password,
password_file, public_key, public_key_file, private_key,
private_key_file, vault_data, datafile_in, datafile_out,
res_find)
# Found the vault
if action == "vault":
@@ -501,16 +671,13 @@ def main():
if not compare_args_ipa(ansible_module, args,
res_find):
commands.append([name, "vault_mod_internal", args])
else:
if 'ipavaultsault' not in args:
args['ipavaultsalt'] = os.urandom(32)
commands.append([name, "vault_add_internal", args])
# archive empty data to set password
pwdargs = data_storage_args(
args, args.get('data', ''), password)
commands.append([name, "vault_archive", pwdargs])
# Set res_find to empty dict for next step # noqa
else:
commands.append([name, "vault_add_internal", args])
if vault_type != 'standard' and vault_data is None:
vault_data = ''
# Set res_find to empty dict for next steps
res_find = {}
# Generate adittion and removal lists
@@ -520,54 +687,98 @@ def main():
group_add, group_del = \
gen_add_del_lists(groups,
res_find.get('member_group', []))
service_add, service_del = \
gen_add_del_lists(services,
res_find.get('member_service', []))
owner_add, owner_del = \
gen_add_del_lists(owners,
res_find.get('owner_user', []))
ownergroups_add, ownergroups_del = \
gen_add_del_lists(ownergroups,
res_find.get('owner_group', []))
ownerservice_add, ownerservice_del = \
gen_add_del_lists(ownerservices,
res_find.get('owner_service', []))
# Add users and groups
if len(user_add) > 0 or len(group_add) > 0:
user_add_args = gen_member_args(args, user_add,
group_add)
commands.append([name, 'vault_add_member',
user_add_args])
user_add_args = gen_member_args(args, user_add,
group_add, service_add)
if user_add_args is not None:
commands.append(
[name, 'vault_add_member', user_add_args])
# Remove users and groups
if len(user_del) > 0 or len(group_del) > 0:
user_del_args = gen_member_args(args, user_del,
group_del)
commands.append([name, 'vault_remove_member',
user_del_args])
user_del_args = gen_member_args(args, user_del,
group_del, service_del)
if user_del_args is not None:
commands.append(
[name, 'vault_remove_member', user_del_args])
# Add owner users and groups
if len(user_add) > 0 or len(group_add) > 0:
owner_add_args = gen_member_args(args, owner_add,
ownergroups_add)
commands.append([name, 'vault_add_owner',
owner_add_args])
owner_add_args = gen_member_args(
args, owner_add, ownergroups_add, ownerservice_add)
if owner_add_args is not None:
# ansible_module.warn("OWNER ADD: %s" % owner_add_args)
commands.append(
[name, 'vault_add_owner', owner_add_args])
# Remove owner users and groups
if len(user_del) > 0 or len(group_del) > 0:
owner_del_args = gen_member_args(args, owner_del,
ownergroups_del)
commands.append([name, 'vault_remove_owner',
owner_del_args])
owner_del_args = gen_member_args(
args, owner_del, ownergroups_del, ownerservice_del)
if owner_del_args is not None:
# ansible_module.warn("OWNER DEL: %s" % owner_del_args)
commands.append(
[name, 'vault_remove_owner', owner_del_args])
if vault_type == 'symmetric' \
and 'ipavaultsalt' not in args:
args['ipavaultsalt'] = os.urandom(32)
if vault_type == 'symmetric' \
and 'ipavaultsalt' not in args:
args['ipavaultsalt'] = os.urandom(32)
elif action in "member":
# Add users and groups
if users is not None or groups is not None:
user_args = gen_member_args(args, users, groups)
if any([users, groups, services]):
user_args = gen_member_args(args, users, groups,
services)
commands.append([name, 'vault_add_member', user_args])
if owners is not None or ownergroups is not None:
owner_args = gen_member_args(args, owners, ownergroups)
if any([owners, ownergroups, ownerservices]):
owner_args = gen_member_args(args, owners, ownergroups,
ownerservices)
commands.append([name, 'vault_add_owner', owner_args])
if vault_data is not None:
data_args = data_storage_args(
args, args.get('data', ''), password)
commands.append([name, 'vault_archive', data_args])
pwdargs = data_storage_args(
args, vault_data, password, password_file, private_key,
private_key_file, datafile_in, datafile_out)
if any([vault_data, datafile_in]):
commands.append([name, "vault_archive", pwdargs])
elif state == "retrieved":
if res_find is None:
ansible_module.fail_json(
msg="Vault `%s` not found to retrieve data." % name)
vault_type = res_find['cn']
# verify data encription args
check_encryption_params(
ansible_module, state, action, vault_type, salt, password,
password_file, public_key, public_key_file, private_key,
private_key_file, vault_data, datafile_in, datafile_out,
res_find)
pwdargs = data_storage_args(
args, vault_data, password, password_file, private_key,
private_key_file, datafile_in, datafile_out)
if 'data' in pwdargs:
del pwdargs['data']
commands.append([name, "vault_retrieve", pwdargs])
elif state == "absent":
if 'ipavaulttype' in args:
@@ -579,32 +790,46 @@ def main():
elif action == "member":
# remove users and groups
if users is not None or groups is not None:
user_args = gen_member_args(args, users, groups)
commands.append([name, 'vault_remove_member',
user_args])
if any([users, groups, services]):
user_args = gen_member_args(
args, users, groups, services)
commands.append(
[name, 'vault_remove_member', user_args])
if owners is not None or ownergroups is not None:
owner_args = gen_member_args(args, owners, ownergroups)
commands.append([name, 'vault_remove_owner',
owner_args])
if any([owners, ownergroups, ownerservices]):
owner_args = gen_member_args(
args, owners, ownergroups, ownerservices)
commands.append(
[name, 'vault_remove_owner', owner_args])
else:
ansible_module.fail_json(
msg="Invalid action '%s' for state '%s'" %
(action, state))
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
ansible_module.fail_json(msg="Unknown state '%s'" % state)
# Execute commands
errors = []
for name, command, args in commands:
try:
# ansible_module.warn("RUN: %s %s %s" % (command, name, args))
result = api_command(ansible_module, command, name, args)
if command == 'vault_archive':
changed = 'Archived data into' in result['summary']
elif command == 'vault_retrieve':
if 'result' not in result:
raise Exception("No result obtained.")
if 'data' in result['result']:
exit_args['data'] = result['result']['data']
elif 'vault_data' in result['result']:
exit_args['data'] = result['result']['vault_data']
else:
raise Exception("No data retrieved.")
changed = False
else:
# ansible_module.warn("RESULT: %s" % (result))
if "completed" in result:
if result["completed"] > 0:
changed = True

2
pytest.ini Normal file
View File

@@ -0,0 +1,2 @@
[pytest]
python_files = test_*.py

View File

@@ -33,9 +33,7 @@ from ansible.plugins.action import ActionBase
def run_cmd(args, stdin=None):
"""
Execute an external command.
"""
"""Execute an external command."""
p_in = None
p_out = subprocess.PIPE
p_err = subprocess.PIPE
@@ -53,8 +51,10 @@ def run_cmd(args, stdin=None):
def kinit_password(principal, password, ccache_name, config):
"""
Perform kinit using principal/password, with the specified config file
and store the TGT in ccache_name.
Perform kinit using principal/password.
It uses the specified config file to kinit and stores the TGT
in ccache_name.
"""
args = ["/usr/bin/kinit", principal, '-c', ccache_name]
old_config = os.environ.get('KRB5_CONFIG')
@@ -71,8 +71,10 @@ def kinit_password(principal, password, ccache_name, config):
def kinit_keytab(principal, keytab, ccache_name, config):
"""
Perform kinit using principal/keytab, with the specified config file
and store the TGT in ccache_name.
Perform kinit using principal/keytab.
It uses the specified config file to kinit and stores the TGT
in ccache_name.
"""
if gssapi is None:
raise ImportError("gssapi is not available")
@@ -126,7 +128,7 @@ class ActionModule(ActionBase):
def run(self, tmp=None, task_vars=None):
"""
handler for credential cache transfer
Handle credential cache transfer.
ipa* commands can either provide a password or a keytab file
in order to authenticate on the managed node with Kerberos.
@@ -142,7 +144,6 @@ class ActionModule(ActionBase):
Then the IPA commands can use this credential cache file.
"""
if task_vars is None:
task_vars = dict()

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python3
# Test ipaclient python3 binding
from ipaclient.install.client import SECURE_PATH
from ipaclient.install.client import SECURE_PATH # noqa: F401
# Check ipapython version to be >= 4.6
from ipapython.version import NUM_VERSION, VERSION

View File

@@ -100,7 +100,6 @@ def main():
realm = module.params.get('realm')
hostname = module.params.get('hostname')
servers = module.params.get('servers')
debug = module.params.get('debug')
host_principal = 'host/%s@%s' % (hostname, realm)

View File

@@ -13,7 +13,7 @@ from ansible.module_utils.basic import AnsibleModule
# pylint: disable=unused-import
try:
from ipalib import api
from ipalib import api # noqa: F401
except ImportError:
HAS_IPALIB = False
else:
@@ -27,7 +27,7 @@ else:
from ipapython import sysrestore
try:
import ipaserver
import ipaserver # noqa: F401
except ImportError:
HAS_IPASERVER = False
else:
@@ -41,7 +41,7 @@ VAR_LIB_PKI_TOMCAT = "/var/lib/pki/pki-tomcat"
def is_ntpd_configured():
# ntpd is configured when sysrestore.state contains the line
# [ntpd]
ntpd_conf_section = re.compile('^\s*\[ntpd\]\s*$')
ntpd_conf_section = re.compile(r'^\s*\[ntpd\]\s*$')
try:
with open(SERVER_SYSRESTORE_STATE) as f:
@@ -56,7 +56,7 @@ def is_ntpd_configured():
def is_dns_configured():
# dns is configured when /etc/named.conf contains the line
# dyndb "ipa" "/usr/lib64/bind/ldap.so" {
bind_conf_section = re.compile('^\s*dyndb\s+"ipa"\s+"[^"]+"\s+{$')
bind_conf_section = re.compile(r'^\s*dyndb\s+"ipa"\s+"[^"]+"\s+{$')
try:
with open(NAMED_CONF) as f:

View File

@@ -135,8 +135,7 @@ if six.PY3:
def get_host_diff(ipa_host, module_host):
"""
Compares two dictionaries containing host attributes and builds a dict
of differences.
Build a dict with the differences from two host dicts.
:param ipa_host: the host structure seen from IPA
:param module_host: the target host structure seen from the module params
@@ -164,7 +163,7 @@ def get_host_diff(ipa_host, module_host):
def get_module_host(module):
"""
Creates a structure representing the host information
Create a structure representing the host information.
Reads the module parameters and builds the host structure as expected from
the module
@@ -189,7 +188,7 @@ def get_module_host(module):
def ensure_host_present(module, api, ipahost):
"""
Ensures that the host exists in IPA and has the same attributes.
Ensure host exists in IPA and has the same attributes.
:param module: the ansible module
:param api: IPA api handle
@@ -246,7 +245,7 @@ def ensure_host_present(module, api, ipahost):
def ensure_host_absent(module, api, host):
"""
Ensures that the host does not exist in IPA
Ensure host does not exist in IPA.
:param module: the ansible module
:param api: the IPA API handle
@@ -271,9 +270,7 @@ def ensure_host_absent(module, api, host):
def main():
"""
Main routine for the ansible module.
"""
module = AnsibleModule(
argument_spec=dict(
principal=dict(default='admin'),
@@ -288,7 +285,6 @@ def main():
supports_check_mode=True,
)
principal = module.params.get('principal', 'admin')
ccache = module.params.get('ccache')
fqdn = unicode(module.params.get('fqdn'))
state = module.params.get('state')

View File

@@ -235,7 +235,6 @@ def is_client_configured():
:returns: boolean
"""
return (os.path.isfile(paths.IPA_DEFAULT_CONF) and
os.path.isfile(os.path.join(paths.IPA_CLIENT_SYSRESTORE,
sysrestore.SYSRESTORE_STATEFILE)))
@@ -243,11 +242,10 @@ def is_client_configured():
def get_ipa_conf():
"""
Return IPA configuration read from /etc/ipa/default.conf
Return IPA configuration read from `/etc/ipa/default.conf`.
:returns: dict containing key,value
"""
parser = RawConfigParser()
parser.read(paths.IPA_DEFAULT_CONF)
result = dict()

View File

@@ -2,9 +2,13 @@
# Test ipaerver python3 binding
try:
from ipaserver.install.server.replicainstall import install_check
from ipaserver.install.server.replicainstall import ( # noqa: F401
install_check,
)
except ImportError:
from ipaserver.install.server.replicainstall import promote_check
from ipaserver.install.server.replicainstall import ( # noqa: F401
promote_check,
)
# Check ipapython version to be >= 4.6
from ipapython.version import NUM_VERSION, VERSION

View File

@@ -123,8 +123,8 @@ def main():
ccache=dict(required=True),
_ca_enabled=dict(required=False, type='bool'),
_ca_file=dict(required=False),
_dirsrv_pkcs12_info=dict(required=False),
_pkinit_pkcs12_info=dict(required=False),
_dirsrv_pkcs12_info=dict(required=False, type='list'),
_pkinit_pkcs12_info=dict(required=False, type='list'),
_top_dir=dict(required=True),
dirman_password=dict(required=True, no_log=True),
ds_ca_subject=dict(required=True),

View File

@@ -119,8 +119,8 @@ def main():
ccache=dict(required=True),
_ca_enabled=dict(required=False, type='bool'),
_ca_file=dict(required=False),
_dirsrv_pkcs12_info=dict(required=False),
_pkinit_pkcs12_info=dict(required=False),
_dirsrv_pkcs12_info=dict(required=False, type='list'),
_pkinit_pkcs12_info=dict(required=False, type='list'),
_top_dir=dict(required=True),
dirman_password=dict(required=True, no_log=True),
ds_ca_subject=dict(required=True),

View File

@@ -106,7 +106,7 @@ def main():
ccache=dict(required=True),
_ca_enabled=dict(required=False, type='bool'),
_ca_file=dict(required=False),
_pkinit_pkcs12_info=dict(required=False),
_pkinit_pkcs12_info=dict(required=False, type='list'),
_top_dir=dict(required=True),
dirman_password=dict(required=True, no_log=True),
),

View File

@@ -195,6 +195,7 @@ import os
import tempfile
import traceback
import six
from shutil import copyfile
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_replica import (
@@ -485,6 +486,21 @@ def main():
"certificate are not signed by the same CA "
"certificate")
# Copy pkcs12_files to make them persistent till deployment is done
# and encode certificates for ansible compatibility
if http_pkcs12_info is not None:
copyfile(http_pkcs12_file.name, "/etc/ipa/.tmp_pkcs12_http")
http_pkcs12_info = ("/etc/ipa/.tmp_pkcs12_http", http_pin)
http_ca_cert = ""
if dirsrv_pkcs12_info is not None:
copyfile(dirsrv_pkcs12_file.name, "/etc/ipa/.tmp_pkcs12_dirsrv")
dirsrv_pkcs12_info = ("/etc/ipa/.tmp_pkcs12_dirsrv", dirsrv_pin)
dirsrv_ca_cert = ""
if pkinit_pkcs12_info is not None:
copyfile(pkinit_pkcs12_file.name, "/etc/ipa/.tmp_pkcs12_pkinit")
pkinit_pkcs12_info = ("/etc/ipa/.tmp_pkcs12_pkinit", pkinit_pin)
pkinit_ca_cert = ""
ansible_log.debug("-- FQDN --")
installutils.verify_fqdn(config.host_name, options.no_host_dns)

View File

@@ -138,8 +138,8 @@ def main():
_ca_file=dict(required=False),
_kra_enabled=dict(required=False, type='bool'),
_kra_host_name=dict(required=False),
_dirsrv_pkcs12_info=dict(required=False),
_pkinit_pkcs12_info=dict(required=False),
_dirsrv_pkcs12_info=dict(required=False, type='list'),
_pkinit_pkcs12_info=dict(required=False, type='list'),
_top_dir=dict(required=True),
_ca_subject=dict(required=True),
_subject_base=dict(required=True),

View File

@@ -118,7 +118,7 @@ def main():
_ca_file=dict(required=False),
_kra_enabled=dict(required=False, type='bool'),
_kra_host_name=dict(required=False),
_pkinit_pkcs12_info=dict(required=False),
_pkinit_pkcs12_info=dict(required=False, type='list'),
_top_dir=dict(required=True),
dirman_password=dict(required=True, no_log=True),
),

View File

@@ -190,7 +190,7 @@ def main():
ccache=dict(required=True),
installer_ccache=dict(required=True),
_ca_enabled=dict(required=False, type='bool'),
_dirsrv_pkcs12_info=dict(required=False),
_dirsrv_pkcs12_info=dict(required=False, type='list'),
_top_dir=dict(required=True),
_add_to_ipaservers=dict(required=True, type='bool'),
_ca_subject=dict(required=True),

View File

@@ -115,7 +115,7 @@ def main():
ccache=dict(required=True),
_ca_enabled=dict(required=False, type='bool'),
_ca_file=dict(required=False),
_http_pkcs12_info=dict(required=False),
_http_pkcs12_info=dict(required=False, type='list'),
_top_dir=dict(required=True),
dirman_password=dict(required=True, no_log=True),
),

View File

@@ -96,7 +96,7 @@ def main():
# additional
config_master_host_name=dict(required=True),
ccache=dict(required=True),
_pkinit_pkcs12_info=dict(required=False),
_pkinit_pkcs12_info=dict(required=False, type='list'),
_top_dir=dict(required=True),
),
supports_check_mode=True,

View File

@@ -407,6 +407,7 @@
ccache: "{{ result_ipareplica_prepare.ccache }}"
_ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
_ca_file: "{{ result_ipareplica_prepare._ca_file }}"
_dirsrv_pkcs12_info: "{{ result_ipareplica_prepare._dirsrv_pkcs12_info }}"
_pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info }}"
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
dirman_password: "{{ ipareplica_dirman_password }}"
@@ -750,6 +751,16 @@
state: absent
when: result_ipareplica_enable_ipa.changed
always:
- name: Cleanup temporary files
file:
path: "{{ item }}"
state: absent
with_items:
- "/etc/ipa/.tmp_pkcs12_dirsrv"
- "/etc/ipa/.tmp_pkcs12_http"
- "/etc/ipa/.tmp_pkcs12_pkinit"
when: not ansible_check_mode and
not (result_ipareplica_test.client_already_configured is defined or
result_ipareplica_test.server_already_configured is defined)

View File

@@ -227,14 +227,17 @@ SSL certificate Variables
Variable | Description | Required
-------- | ----------- | --------
`ipaserver_dirsrv_cert_files` | Files containing the Directory Server SSL certificate and private keys. (list of strings) | no
`ipaserver_http_cert_file` | File containing the Apache Server SSL certificate and private key. (string) | no
`ipaserver_pkinit_cert_file` | File containing the Kerberos KDC SSL certificate and private key. (string) | no
`ipaserver_http_cert_files` | File containing the Apache Server SSL certificate and private key. (list of string) | no
`ipaserver_pkinit_cert_files` | File containing the Kerberos KDC SSL certificate and private key. (list of string) | no
`ipaserver_dirsrv_pin` | The password to unlock the Directory Server private key. (string) | no
`ipaserver_http_pin` | The password to unlock the Apache Server private key. (string) | no
`ipaserver_pkinit_pin` | The password to unlock the Kerberos KDC private key. (string) | no
`ipaserver_dirsrv_cert_name` | Name of the Directory Server SSL certificate to install. (string) | no
`ipaserver_http_cert_name` | Name of the Apache Server SSL certificate to install. (string) | no
`ipaserver_pkinit_cert_name` | Name of the Kerberos KDC SSL certificate to install. (string) | no
`ipaserver_no_pkinit` | Disable pkinit setup steps (boolean) | no
NOTE: If one of the `ipaserver_http_cert_files` or `ipaserver_pkinit_cert_files` is specified, then both are required, so declaring only one of them will raise an error. Additionally, one of `ipaserver_pkinit_cert_files` or `ipaserver_no_pkinit` must be provided as well.
Client Variables
----------------

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python3
# Test ipaerver python3 binding
from ipaserver.install.server.install import install_check
from ipaserver.install.server.install import install_check # noqa: F401
# Check ipapython version to be >= 4.6
from ipapython.version import NUM_VERSION, VERSION

View File

@@ -127,7 +127,7 @@ def main():
no_hbac_allow=dict(required=False, type='bool', default=False),
no_pkinit=dict(required=False, type='bool', default=False),
dirsrv_config_file=dict(required=False),
_dirsrv_pkcs12_info=dict(required=False),
_dirsrv_pkcs12_info=dict(required=False, type='list'),
# ssl certificate
dirsrv_cert_files=dict(required=False, type='list', default=[]),
subject_base=dict(required=False),

View File

@@ -163,7 +163,7 @@ from ansible.module_utils.ansible_ipa_server import (
AnsibleModuleLog, setup_logging, options, sysrestore, paths,
ansible_module_get_parsed_ip_addresses,
api_Backend_ldap2, redirect_stdout, ca, installutils, ds_init_info,
custodiainstance, write_cache, x509
custodiainstance, write_cache, x509, decode_certificate
)
@@ -191,7 +191,7 @@ def main():
no_pkinit=dict(required=False, type='bool', default=False),
dirsrv_config_file=dict(required=False),
dirsrv_cert_files=dict(required=False, type='list'),
_dirsrv_pkcs12_info=dict(required=False),
_dirsrv_pkcs12_info=dict(required=False, type='list'),
# certificate system
external_ca=dict(required=False, type='bool', default=False),
external_ca_type=dict(required=False),
@@ -265,8 +265,8 @@ def main():
# additional
options.domainlevel = ansible_module.params.get('domainlevel')
options._http_ca_cert = ansible_module.params.get('_http_ca_cert')
# tions._update_hosts_file = ansible_module.params.get(
# 'update_hosts_file')
if options._http_ca_cert:
options._http_ca_cert = decode_certificate(options._http_ca_cert)
# init #################################################################

View File

@@ -126,7 +126,7 @@ def main():
dirsrv_config_file=dict(required=False),
# ssl certificate
dirsrv_cert_files=dict(required=False, type='list', default=[]),
_dirsrv_pkcs12_info=dict(required=False),
_dirsrv_pkcs12_info=dict(required=False, type='list'),
# certificate system
external_cert_files=dict(required=False, type='list', default=[]),
subject_base=dict(required=False),

View File

@@ -199,8 +199,8 @@ def main():
# _update_hosts_file=dict(required=False, type='bool',
# default=False),
_dirsrv_pkcs12_info=dict(required=False),
_http_pkcs12_info=dict(required=False),
_dirsrv_pkcs12_info=dict(required=False, type='list'),
_http_pkcs12_info=dict(required=False, type='list'),
),
)

View File

@@ -160,7 +160,7 @@ def main():
no_reverse=dict(required=False, type='bool', default=False),
auto_forwarders=dict(required=False, type='bool', default=False),
_pkinit_pkcs12_info=dict(required=False),
_pkinit_pkcs12_info=dict(required=False, type='list'),
),
)

View File

@@ -209,6 +209,7 @@ import sys
import six
import inspect
import random
from shutil import copyfile
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
@@ -219,7 +220,8 @@ from ansible.module_utils.ansible_ipa_server import (
NUM_VERSION, is_ipa_configured, sysrestore, paths, bindinstance,
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
validate_domain_name, load_pkcs12, IPA_PYTHON_VERSION,
encode_certificate
)
if six.PY3:
@@ -252,7 +254,7 @@ def main():
dirsrv_config_file=dict(required=False),
# ssl certificate
dirsrv_cert_files=dict(required=False, type='list', default=None),
http_cert_files=dict(required=False, type='list', defaullt=None),
http_cert_files=dict(required=False, type='list', default=None),
pkinit_cert_files=dict(required=False, type='list', default=None),
dirsrv_pin=dict(required=False),
http_pin=dict(required=False),
@@ -1013,6 +1015,21 @@ def main():
# done ##################################################################
# Copy pkcs12_files to make them persistent till deployment is done
# and encode certificates for ansible compatibility
if http_pkcs12_info is not None:
copyfile(http_pkcs12_file.name, "/etc/ipa/.tmp_pkcs12_http")
http_pkcs12_info = ("/etc/ipa/.tmp_pkcs12_http", http_pin)
http_ca_cert = encode_certificate(http_ca_cert)
if dirsrv_pkcs12_info is not None:
copyfile(dirsrv_pkcs12_file.name, "/etc/ipa/.tmp_pkcs12_dirsrv")
dirsrv_pkcs12_info = ("/etc/ipa/.tmp_pkcs12_dirsrv", dirsrv_pin)
dirsrv_ca_cert = encode_certificate(dirsrv_ca_cert)
if pkinit_pkcs12_info is not None:
copyfile(pkinit_pkcs12_file.name, "/etc/ipa/.tmp_pkcs12_pkinit")
pkinit_pkcs12_info = ("/etc/ipa/.tmp_pkcs12_pkinit", pkinit_pin)
pkinit_ca_cert = encode_certificate(pkinit_ca_cert)
ansible_module.exit_json(changed=False,
ipa_python_version=IPA_PYTHON_VERSION,
# basic

View File

@@ -37,11 +37,13 @@ __all__ = ["IPAChangeConf", "certmonger", "sysrestore", "root_logger",
"validate_dm_password", "read_cache", "write_cache",
"adtrustinstance", "IPAAPI_USER", "sync_time", "PKIIniLoader",
"default_subject_base", "default_ca_subject_dn",
"check_ldap_conf"]
"check_ldap_conf", "encode_certificate", "decode_certificate"]
import sys
import logging
from contextlib import contextmanager as contextlib_contextmanager
import six
import base64
from ipapython.version import NUM_VERSION, VERSION
@@ -137,6 +139,17 @@ if NUM_VERSION >= 40500:
except ImportError:
check_ldap_conf = None
try:
from ipalib.x509 import Encoding
except ImportError:
from cryptography.hazmat.primitives.serialization import Encoding
try:
from ipalib.x509 import load_pem_x509_certificate
except ImportError:
from ipalib.x509 import load_certificate
load_pem_x509_certificate = None
else:
# IPA version < 4.5
@@ -322,3 +335,41 @@ def ansible_module_get_parsed_ip_addresses(ansible_module,
ansible_module.fail_json(msg="Invalid IP Address %s: %s" % (ip, e))
ip_addrs.append(ip_parsed)
return ip_addrs
def encode_certificate(cert):
"""
Encode a certificate using base64.
It also takes FreeIPA and Python versions into account.
"""
if isinstance(cert, (str, bytes)):
encoded = base64.b64encode(cert)
else:
encoded = base64.b64encode(cert.public_bytes(Encoding.DER))
if not six.PY2:
encoded = encoded.decode('ascii')
return encoded
def decode_certificate(cert):
"""
Decode a certificate using base64.
It also takes FreeIPA versions into account and returns a IPACertificate
for newer IPA versions.
"""
if hasattr(x509, "IPACertificate"):
cert = cert.strip()
if not cert.startswith("-----BEGIN CERTIFICATE-----"):
cert = "-----BEGIN CERTIFICATE-----\n" + cert
if not cert.endswith("-----END CERTIFICATE-----"):
cert += "\n-----END CERTIFICATE-----"
if load_pem_x509_certificate is not None:
cert = load_pem_x509_certificate(cert.encode('utf-8'))
else:
cert = load_certificate(cert.encode('utf-8'))
else:
cert = base64.b64decode(cert)
return cert

View File

@@ -286,6 +286,7 @@
reverse_zones: "{{ result_ipaserver_prepare.reverse_zones }}"
no_reverse: "{{ ipaserver_no_reverse }}"
auto_forwarders: "{{ ipaserver_auto_forwarders }}"
_http_ca_cert: "{{ result_ipaserver_test._http_ca_cert }}"
register: result_ipaserver_setup_ca
- name: Copy /root/ipa.csr to "{{ inventory_hostname }}-ipa.csr"
@@ -448,6 +449,16 @@
when: not result_ipaserver_setup_ca.csr_generated | bool
always:
- name: Cleanup temporary files
file:
path: "{{ item }}"
state: absent
with_items:
- "/etc/ipa/.tmp_pkcs12_dirsrv"
- "/etc/ipa/.tmp_pkcs12_http"
- "/etc/ipa/.tmp_pkcs12_pkinit"
when: not ansible_check_mode and not
(not result_ipaserver_test.changed and
(result_ipaserver_test.client_already_configured is defined or

View File

@@ -20,3 +20,13 @@ data_files =
/usr/share/ansible/roles/ipaclient = roles/ipaclient/*
/usr/share/ansible/roles/ipaserver = roles/ipaserver/*
/usr/share/ansible/roles/ipareplica = roles/ipareplica/*
[flake8]
extend-ignore = E203
per-file-ignores =
plugins/*:E402
roles/*:E402
[pydocstyle]
inherit = false
ignore = D1,D212,D203

58
tests/README.md Normal file
View File

@@ -0,0 +1,58 @@
# Running the tests
## Before starting
In order to run ansible-freeipa tests you will need to have `ansible` and `pytest` installed on your machine. We'll call this local machine `controller`.
You will also need to have a remote host with freeipa server installed and configured. We'll call this remote host `ipaserver`.
Some other requirements:
* The `controller` must be able to connect to `ipaserver` through ssh using keys.
* `ipaserver` must be configured with DNS support. See [ipaserver role](../roles/ipaserver/README.md).
* IPA admin password must be `SomeADMINpassword`.
* Directory Server admin password must be `SomeDMpassword`.
## Running the tests
To run the tests run:
```
IPA_SERVER_HOST=<ipaserver_host_or_ip> pytest
```
If you need to run using a different user you can use `ANSIBLE_REMOTE_USER`
environment variable. For example:
```
ANSIBLE_REMOTE_USER=root IPA_SERVER_HOST=<ipaserver_host_or_ip> pytest
```
To select which tests to run use the option `-k`. For example:
```
IPA_SERVER_HOST=<ipaserver_host_or_ip> pytest -k dnszone
```
To see the ansible output use the option `--capture=sys`. For example:
```
IPA_SERVER_HOST=<ipaserver_host_or_ip> pytest --capture=sys
```
To see why tests were skipped use `-rs`. For example:
```
IPA_SERVER_HOST=<ipaserver_host_or_ip> pytest -rs
```
For a complete list of options check `pytest --help`.
## Upcoming/desired improvements:
* A script to pre-config the complete test environment using virsh.
* A test matrix to run tests against different distros in parallel (probably using tox).
* Allow to connect to `ipaserver` using ssh and password.

5
tests/ansible.cfg Normal file
View File

@@ -0,0 +1,5 @@
[defaults]
roles_path = ../roles:~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles
library = ../plugins/modules:~/.ansible/plugins/modules:/usr/share/ansible/plugins/modules
module_utils = ../plugins/module_utils:~/.ansible/plugins/module_utils:/usr/share/ansible/plugins/module_utils
host_key_checking = false

View File

@@ -0,0 +1,20 @@
[kdc_cert]
basicConstraints=CA:FALSE
keyUsage=nonRepudiation,digitalSignature,keyEncipherment,keyAgreement
extendedKeyUsage=1.3.6.1.5.2.3.5
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
issuerAltName=issuer:copy
subjectAltName=otherName:1.3.6.1.5.2.2;SEQUENCE:kdc_princ_name
[kdc_princ_name]
realm=EXP:0,GeneralString:${ENV::REALM}
principal_name=EXP:1,SEQUENCE:kdc_principal_seq
[kdc_principal_seq]
name_type=EXP:0,INTEGER:1
name_string=EXP:1,SEQUENCE:kdc_principals
[kdc_principals]
princ1=GeneralString:krbtgt
princ2=GeneralString:${ENV::REALM}

View File

@@ -0,0 +1,15 @@
---
- name: Clean up certificates
hosts: localhost
gather_facts: false
tasks:
- name: Run generate-certificates.sh
command: >
/bin/bash
generate-certificates.sh delete "{{ item }}"
args:
chdir: "{{ playbook_dir }}"
with_items:
- "{{ groups.ipaserver[0] }}"
- "{{ groups.ipareplicas[0] }}"

View File

@@ -0,0 +1,153 @@
#!/usr/bin/env bash
ROOT_CA_DIR="certificates/root-ca"
DIRSRV_CERTS_DIR="certificates/dirsrv"
HTTPD_CERTS_DIR="certificates/httpd"
PKINIT_CERTS_DIR="certificates/pkinit"
PKCS12_PASSWORD="SomePKCS12password"
# generate_ipa_pkcs12_certificate \
# $cert_name $ipa_fqdn $certs_dir $root_ca_cert $root_ca_private_key extensions_file extensions_name
function generate_ipa_pkcs12_certificate {
cert_name=$1
ipa_fqdn=$2
certs_dir=$3
root_ca_cert=$4
root_ca_private_key=$5
extensions_file=$6
extensions_name=$7
# Generate CSR and private key
openssl req -new -newkey rsa:4096 -nodes \
-subj "/C=US/ST=Test/L=Testing/O=Default/CN=${ipa_fqdn}" \
-keyout ${certs_dir}/private.key \
-out ${certs_dir}/request.csr
# Sign CSR to generate PEM certificate
if [ -z "${extensions_file}" ]; then
openssl x509 -req -days 365 -sha256 \
-CAcreateserial \
-CA ${root_ca_cert} \
-CAkey ${root_ca_private_key} \
-in ${certs_dir}/request.csr \
-out ${certs_dir}/cert.pem
else
openssl x509 -req -days 365 -sha256 \
-CAcreateserial \
-CA ${ROOT_CA_DIR}/cert.pem \
-CAkey ${ROOT_CA_DIR}/private.key \
-extfile ${extensions_file} \
-extensions ${extensions_name} \
-in ${certs_dir}/request.csr \
-out ${certs_dir}/cert.pem
fi
# Convert certificate to PKCS12 format
openssl pkcs12 -export \
-name ${cert_name} \
-certfile ${root_ca_cert} \
-in ${certs_dir}/cert.pem \
-inkey ${certs_dir}/private.key \
-passout "pass:${PKCS12_PASSWORD}" \
-out ${certs_dir}/cert.p12
}
# generate_ipa_pkcs12_certificates $ipa_fqdn $ipa_domain
function generate_ipa_pkcs12_certificates {
host=$1
if [ -z "$host" ]; then
echo "ERROR: ipa-host-fqdn is not set"
echo
echo "usage: $0 create ipa-host-fqdn domain"
exit 0;
fi
domain=$2
if [ -z "$domain" ]; then
echo "ERROR: domain is not set"
echo
echo "usage: $0 create ipa-host-fqdn domain"
exit 0;
fi
# Generate certificates folder structure
mkdir -p ${ROOT_CA_DIR}
mkdir -p ${DIRSRV_CERTS_DIR}/$host
mkdir -p ${HTTPD_CERTS_DIR}/$host
mkdir -p ${PKINIT_CERTS_DIR}/$host
# Generate root CA
if [ ! -f "${ROOT_CA_DIR}/private.key" ]; then
openssl genrsa \
-out ${ROOT_CA_DIR}/private.key 4096
openssl req -new -x509 -sha256 -nodes -days 3650 \
-subj "/C=US/ST=Test/L=Testing/O=Default" \
-key ${ROOT_CA_DIR}/private.key \
-out ${ROOT_CA_DIR}/cert.pem
fi
# Generate a certificate for the Directory Server
if [ ! -f "${DIRSRV_CERTS_DIR}/$host/cert.pem" ]; then
generate_ipa_pkcs12_certificate \
"dirsrv-cert" \
$host \
"${DIRSRV_CERTS_DIR}/$host" \
"${ROOT_CA_DIR}/cert.pem" \
"${ROOT_CA_DIR}/private.key"
fi
# Generate a certificate for the Apache server
if [ ! -f "${HTTPD_CERTS_DIR}/$host/cert.pem" ]; then
generate_ipa_pkcs12_certificate \
"httpd-cert" \
$host \
"${HTTPD_CERTS_DIR}/$host" \
"${ROOT_CA_DIR}/cert.pem" \
"${ROOT_CA_DIR}/private.key"
fi
# Generate a certificate for the KDC PKINIT
if [ ! -f "${PKINIT_CERTS_DIR}/$host/cert.pem" ]; then
export REALM=${domain^^}
generate_ipa_pkcs12_certificate \
"pkinit-cert" \
$host \
"${PKINIT_CERTS_DIR}/$host" \
"${ROOT_CA_DIR}/cert.pem" \
"${ROOT_CA_DIR}/private.key" \
"${PKINIT_CERTS_DIR}/extensions.conf" \
"kdc_cert"
fi
}
# delete_ipa_pkcs12_certificates $ipa_fqdn
function delete_ipa_pkcs12_certificates {
host=$1
if [ -z "$host" ]; then
echo "ERROR: ipa-host-fqdn is not set"
echo
echo "usage: $0 delete ipa-host-fqdn"
exit 0;
fi
rm -f certificates/*/$host/*
rm -f ${ROOT_CA_DIR}/*
}
# Entrypoint
case "$1" in
create)
generate_ipa_pkcs12_certificates $2 $3
;;
delete)
delete_ipa_pkcs12_certificates $2
;;
*)
echo $"Usage: $0 {create|delete}"
;;
esac

View File

@@ -0,0 +1,82 @@
---
- name: Generate certificates
hosts: localhost
gather_facts: false
tasks:
- name: Run generate-certificates.sh
command: >
/bin/bash
generate-certificates.sh create
"{{ groups.ipareplicas[0] }}"
"{{ ipareplica_domain | default(groups.ipareplicas[0].split('.')[1:] | join ('.')) }}"
args:
chdir: "{{ playbook_dir }}"
- name: Test ipareplicas installation without CA
hosts: ipareplicas
become: true
vars:
# Root CA certificate
ipareplica_ca_cert_files:
- /root/ca-less-test/ca.crt
# Directory server certificates
ipareplica_dirsrv_cert_name: dirsrv-cert
ipareplica_dirsrv_cert_files:
- /root/ca-less-test/dirsrv.p12
ipareplica_dirsrv_pin: SomePKCS12password
# Apache certificates
ipareplica_http_cert_name: httpd-cert
ipareplica_http_cert_files:
- /root/ca-less-test/httpd.p12
ipareplica_http_pin: SomePKCS12password
# PKINIT configuration
ipareplica_no_pkinit: no
ipareplica_pkinit_cert_name: pkinit-cert
ipareplica_pkinit_cert_files:
- /root/ca-less-test/pkinit.p12
ipareplica_pkinit_pin: SomePKCS12password
pre_tasks:
- name: Remove "/root/ca-less-test"
file:
path: "/root/ca-less-test"
state: absent
- name: Generate "/root/ca-less-test"
file:
path: "/root/ca-less-test"
state: directory
- name: Copy CA certificate
copy:
src: "{{ playbook_dir }}/certificates/root-ca/cert.pem"
dest: "/root/ca-less-test/ca.crt"
owner: root
group: root
mode: "0644"
- name: Copy p12 certificates
copy:
src: "{{ playbook_dir }}/certificates/{{ item }}/{{ groups.ipareplicas[0] }}/cert.p12"
dest: "/root/ca-less-test/{{ item }}.p12"
owner: root
group: root
mode: "0644"
with_items:
- dirsrv
- httpd
- pkinit
roles:
- role: ipareplica
state: present
post_tasks:
- name: Fix KDC certificate permissions
file:
path: /var/kerberos/krb5kdc/kdc.crt
owner: root
group: root
mode: '0644'

View File

@@ -0,0 +1,74 @@
---
- name: Generate certificates
hosts: localhost
gather_facts: false
tasks:
- name: Run generate-certificates.sh
command: >
/bin/bash
generate-certificates.sh create
"{{ groups.ipaserver[0] }}"
"{{ ipaserver_domain | default(groups.ipaserver[0].split('.')[1:] | join ('.')) }}"
args:
chdir: "{{ playbook_dir }}"
- name: Test ipaserver installation without CA
hosts: ipaserver
become: true
vars:
# Root CA certificate
ipaserver_ca_cert_files:
- /root/ca-less-test/ca.crt
# Directory server certificates
ipaserver_dirsrv_cert_name: dirsrv-cert
ipaserver_dirsrv_cert_files:
- /root/ca-less-test/dirsrv.p12
ipaserver_dirsrv_pin: SomePKCS12password
# Apache certificates
ipaserver_http_cert_name: httpd-cert
ipaserver_http_cert_files:
- /root/ca-less-test/httpd.p12
ipaserver_http_pin: SomePKCS12password
# PKINIT configuration
ipaserver_no_pkinit: no
ipaserver_pkinit_cert_name: pkinit-cert
ipaserver_pkinit_cert_files:
- /root/ca-less-test/pkinit.p12
ipaserver_pkinit_pin: SomePKCS12password
pre_tasks:
- name: Remove "/root/ca-less-test"
file:
path: "/root/ca-less-test"
state: absent
- name: Generate "/root/ca-less-test"
file:
path: "/root/ca-less-test"
state: directory
- name: Copy CA certificate
copy:
src: "{{ playbook_dir }}/certificates/root-ca/cert.pem"
dest: "/root/ca-less-test/ca.crt"
owner: root
group: root
mode: "0644"
- name: Copy p12 certificates
copy:
src: "{{ playbook_dir }}/certificates/{{ item }}/{{ groups.ipaserver[0] }}/cert.p12"
dest: "/root/ca-less-test/{{ item }}.p12"
owner: root
group: root
mode: "0644"
with_items:
- dirsrv
- httpd
- pkinit
roles:
- role: ipaserver
state: present

17
tests/ca-less/inventory Normal file
View File

@@ -0,0 +1,17 @@
[ipaserver]
ipaserver.test.local
[ipaserver:vars]
ipaserver_domain=test.local
ipaserver_realm=TEST.LOCAL
ipaadmin_password=SomeADMINpassword
ipadm_password=SomeDMpassword
[ipareplicas]
ipareplica.test.local
[ipareplicas:vars]
ipareplica_domain=test.local
ipareplica_realm=TEST.LOCAL
ipaadmin_password=SomeADMINpassword
ipadm_password=SomeDMpassword

View File

@@ -0,0 +1,388 @@
---
- name: Playbook to handle server configuration
hosts: ipaserver
become: true
gather_facts: false
tasks:
# Retrieve current configuration.
- name: return current values of the global configuration options
ipaconfig:
ipaadmin_password: SomeADMINpassword
register: previousconfig
- debug:
msg: "{{previousconfig}}"
# setup environment.
- name: create test group
ipagroup:
ipaadmin_password: 'SomeADMINpassword'
name: somedefaultgroup
- name: Ensure the default e-mail domain is ipa.test.
ipaconfig:
ipaadmin_password: SomeADMINpassword
emaildomain: ipa.test
- name: set default shell to '/bin/sh'
ipaconfig:
ipaadmin_password: SomeADMINpassword
defaultshell: /bin/sh
- name: set default group
ipaconfig:
ipaadmin_password: SomeADMINpassword
defaultgroup: ipausers
- name: set default home directory
ipaconfig:
ipaadmin_password: SomeADMINpassword
homedirectory: /home
- name: clear pac-type
ipaconfig:
ipaadmin_password: SomeADMINpassword
pac_type: ""
- name: set maxusername to 255
ipaconfig:
ipaadmin_password: SomeADMINpassword
maxusername: 255
- name: set maxhostname to 255
ipaconfig:
ipaadmin_password: SomeADMINpassword
maxhostname: 255
- name: set pwdexpnotify to 0
ipaconfig:
ipaadmin_password: SomeADMINpassword
pwdexpnotify: 0
- name: set searchrecordslimit to 10
ipaconfig:
ipaadmin_password: SomeADMINpassword
searchrecordslimit: 10
- name: set searchtimelimit to 1
ipaconfig:
ipaadmin_password: SomeADMINpassword
searchtimelimit: 1
- name: clear configstring
ipaconfig:
ipaadmin_password: SomeADMINpassword
configstring: ""
- name: set configstring to AllowNThash
ipaconfig:
ipaadmin_password: SomeADMINpassword
configstring: 'KDC:Disable Lockout'
- name: set selinuxusermapdefault
ipaconfig:
ipaadmin_password: SomeADMINpassword
selinuxusermapdefault: "staff_u:s0-s0:c0.c1023"
- name: set selinuxusermaporder
ipaconfig:
ipaadmin_password: SomeADMINpassword
selinuxusermaporder: 'user_u:s0$staff_u:s0-s0:c0.c1023'
- name: set usersearch to `uid`
ipaconfig:
ipaadmin_password: SomeADMINpassword
usersearch: uid
- name: set groupsearch to `cn`
ipaconfig:
ipaadmin_password: SomeADMINpassword
groupsearch: cn
# tests
- name: Ensure the default e-mail domain is somedomain.test.
ipaconfig:
ipaadmin_password: SomeADMINpassword
emaildomain: somedomain.test
register: result
failed_when: not result.changed
- name: Ensure the default e-mail domain is somedomain.test, again.
ipaconfig:
ipaadmin_password: SomeADMINpassword
emaildomain: somedomain.test
register: result
failed_when: result.changed
- name: set default shell to '/bin/someshell'
ipaconfig:
ipaadmin_password: SomeADMINpassword
defaultshell: /bin/someshell
register: result
failed_when: not result.changed
- name: set default shell to '/bin/someshell', again.
ipaconfig:
ipaadmin_password: SomeADMINpassword
defaultshell: /bin/someshell
register: result
failed_when: result.changed
- name: set default group
ipaconfig:
ipaadmin_password: SomeADMINpassword
defaultgroup: somedefaultgroup
register: result
failed_when: not result.changed
- name: set default group
ipaconfig:
ipaadmin_password: SomeADMINpassword
defaultgroup: somedefaultgroup
register: result
failed_when: result.changed
- name: set default home directory
ipaconfig:
ipaadmin_password: SomeADMINpassword
homedirectory: /Users
register: result
failed_when: not result.changed
- name: set default home directory
ipaconfig:
ipaadmin_password: SomeADMINpassword
homedirectory: /Users
register: result
failed_when: result.changed
- name: set pac-type
ipaconfig:
ipaadmin_password: SomeADMINpassword
pac_type: "nfs:NONE"
register: result
failed_when: not result.changed
- name: set pac-type, again.
ipaconfig:
ipaadmin_password: SomeADMINpassword
pac_type: "nfs:NONE"
register: result
failed_when: result.changed
- name: set maxusername to 33
ipaconfig:
ipaadmin_password: SomeADMINpassword
maxusername: 33
register: result
failed_when: not result.changed
- name: set maxusername to 33, again.
ipaconfig:
ipaadmin_password: SomeADMINpassword
maxusername: 33
register: result
failed_when: result.changed
- name: set maxhostname to 77
ipaconfig:
ipaadmin_password: SomeADMINpassword
maxhostname: 77
register: result
failed_when: not result.changed
- name: set maxhostname to 77, again
ipaconfig:
ipaadmin_password: SomeADMINpassword
maxhostname: 77
register: result
failed_when: result.changed
- name: set pwdexpnotify to 17
ipaconfig:
ipaadmin_password: SomeADMINpassword
pwdexpnotify: 17
register: result
failed_when: not result.changed
- name: set pwdexpnotify to 17, again
ipaconfig:
ipaadmin_password: SomeADMINpassword
pwdexpnotify: 17
register: result
failed_when: result.changed
- name: set searchrecordslimit to -1
ipaconfig:
ipaadmin_password: SomeADMINpassword
searchrecordslimit: -1
register: result
failed_when: not result.changed
- name: set searchrecordslimit to -1, again.
ipaconfig:
ipaadmin_password: SomeADMINpassword
searchrecordslimit: -1
register: result
failed_when: result.changed
- name: set searchtimelimit to 12345
ipaconfig:
ipaadmin_password: SomeADMINpassword
searchtimelimit: 12345
register: result
failed_when: not result.changed
- name: set searchtimelimit to 12345, again.
ipaconfig:
ipaadmin_password: SomeADMINpassword
searchtimelimit: 12345
register: result
failed_when: result.changed
- name: change enable_migration
ipaconfig:
ipaadmin_password: SomeADMINpassword
enable_migration: '{{ not previousconfig.config.enable_migration }}'
register: result
failed_when: not result.changed
- name: change enable_migration, again
ipaconfig:
ipaadmin_password: SomeADMINpassword
enable_migration: '{{ not previousconfig.config.enable_migration }}'
register: result
failed_when: result.changed
- name: set configstring to AllowNThash
ipaconfig:
ipaadmin_password: SomeADMINpassword
configstring: AllowNThash
register: result
failed_when: not result.changed
- name: set configstring to AllowNThash, again.
ipaconfig:
ipaadmin_password: SomeADMINpassword
configstring: AllowNThash
register: result
failed_when: result.changed
- name: set selinuxusermaporder
ipaconfig:
ipaadmin_password: SomeADMINpassword
selinuxusermaporder: 'user_u:s0$staff_u:s0-s0:c0.c1023$sysadm_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023'
register: result
failed_when: not result.changed
- name: set selinuxusermaporder, again
ipaconfig:
ipaadmin_password: SomeADMINpassword
selinuxusermaporder: 'user_u:s0$staff_u:s0-s0:c0.c1023$sysadm_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023'
register: result
failed_when: result.changed
- name: set selinuxusermapdefault
ipaconfig:
ipaadmin_password: SomeADMINpassword
selinuxusermapdefault: 'user_u:s0'
register: result
failed_when: not result.changed
- name: set selinuxusermapdefault, again
ipaconfig:
ipaadmin_password: SomeADMINpassword
selinuxusermapdefault: 'user_u:s0'
register: result
failed_when: result.changed
- name: set groupsearch to `description`
ipaconfig:
ipaadmin_password: SomeADMINpassword
groupsearch: description
register: result
failed_when: not result.changed
- name: set groupsearch to `gidNumber`, again
ipaconfig:
ipaadmin_password: SomeADMINpassword
groupsearch: description
register: result
failed_when: result.changed
- name: set usersearch to `uidNumber`
ipaconfig:
ipaadmin_password: SomeADMINpassword
usersearch: uidNumber
register: result
failed_when: not result.changed
- name: set usersearch to `uidNumber`, again
ipaconfig:
ipaadmin_password: SomeADMINpassword
usersearch: uidNumber
register: result
failed_when: result.changed
- name: reset changed fields
ipaconfig:
ipaadmin_password: 'SomeADMINpassword'
maxusername: '{{previousconfig.config.maxusername | default(omit)}}'
maxhostname: '{{previousconfig.config.maxhostname | default(omit)}}'
homedirectory: '{{previousconfig.config.homedirectory | default(omit)}}'
defaultshell: '{{previousconfig.config.defaultshell | default(omit)}}'
defaultgroup: '{{previousconfig.config.defaultgroup | default(omit)}}'
emaildomain: '{{previousconfig.config.emaildomain | default(omit)}}'
searchtimelimit: '{{previousconfig.config.searchtimelimit | default(omit)}}'
searchrecordslimit: '{{previousconfig.config.searchrecordslimit | default(omit)}}'
usersearch: '{{previousconfig.config.usersearch | default(omit)}}'
groupsearch: '{{previousconfig.config.groupsearch | default(omit)}}'
enable_migration: '{{previousconfig.config.enable_migration | default(omit)}}'
groupobjectclasses: '{{previousconfig.config.groupobjectclasses | default(omit)}}'
userobjectclasses: '{{previousconfig.config.userobjectclasses | default(omit)}}'
pwdexpnotify: '{{previousconfig.config.pwdexpnotify | default(omit)}}'
configstring: '{{previousconfig.config.configstring | default(omit)}}'
selinuxusermapdefault: '{{previousconfig.config.selinuxusermapdefault | default(omit)}}'
selinuxusermaporder: '{{previousconfig.config.selinuxusermaporder | default(omit)}}'
pac_type: '{{previousconfig.config.pac_type | default(omit)}}'
user_auth_type: '{{previousconfig.config.user_auth_type | default(omit)}}'
domain_resolution_order: '{{previousconfig.config.domain_resolution_order | default(omit)}}'
ca_renewal_master_server: '{{previousconfig.config.ca_renewal_master_server | default(omit)}}'
register: result
failed_when: not result.changed
- name: reset changed fields, again
ipaconfig:
ipaadmin_password: 'SomeADMINpassword'
maxusername: '{{previousconfig.config.maxusername | default(omit)}}'
maxhostname: '{{previousconfig.config.maxhostname | default(omit)}}'
homedirectory: '{{previousconfig.config.homedirectory | default(omit)}}'
defaultshell: '{{previousconfig.config.defaultshell | default(omit)}}'
defaultgroup: '{{previousconfig.config.defaultgroup | default(omit)}}'
emaildomain: '{{previousconfig.config.emaildomain | default(omit)}}'
searchtimelimit: '{{previousconfig.config.searchtimelimit | default(omit)}}'
searchrecordslimit: '{{previousconfig.config.searchrecordslimit | default(omit)}}'
usersearch: '{{previousconfig.config.usersearch | default(omit)}}'
groupsearch: '{{previousconfig.config.groupsearch | default(omit)}}'
enable_migration: '{{previousconfig.config.enable_migration | default(omit)}}'
groupobjectclasses: '{{previousconfig.config.groupobjectclasses | default(omit)}}'
userobjectclasses: '{{previousconfig.config.userobjectclasses | default(omit)}}'
pwdexpnotify: '{{previousconfig.config.pwdexpnotify | default(omit)}}'
configstring: '{{previousconfig.config.configstring | default(omit)}}'
selinuxusermapdefault: '{{previousconfig.config.selinuxusermapdefault | default(omit)}}'
selinuxusermaporder: '{{previousconfig.config.selinuxusermaporder | default(omit)}}'
pac_type: '{{previousconfig.config.pac_type | default(omit)}}'
user_auth_type: '{{previousconfig.config.user_auth_type | default(omit)}}'
domain_resolution_order: '{{previousconfig.config.domain_resolution_order | default(omit)}}'
ca_renewal_master_server: '{{previousconfig.config.ca_renewal_master_server | default(omit)}}'
register: result
failed_when: result.changed
# cleanup
- name: cleanup test group
ipagroup:
ipaadmin_password: 'SomeADMINpassword'
name: somedefaultgroup
state: absent

View File

@@ -18,6 +18,29 @@
state: absent
# Tests.
- name: Set config to invalid IPv4.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
forwarders:
- ip_address: 1.2.3.500
register: result
failed_when: not result.failed or "Invalid IP for DNS forwarder" not in result.msg
- name: Set config to invalid IP.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
forwarders:
- ip_address: 1.in.va.lid
register: result
failed_when: not result.failed or "Invalid IP for DNS forwarder" not in result.msg
- name: Set config to invalid IPv6.
ipadnsconfig:
ipaadmin_password: SomeADMINpassword
forwarders:
- ip_address: fd00::invalid
register: result
failed_when: not result.failed or "Invalid IP for DNS forwarder" not in result.msg
- name: Set dnsconfig.
ipadnsconfig:

View File

@@ -0,0 +1,135 @@
---
# Cleanup tasks.
- name: Ensure that dns records are absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: "{{ testzone }}"
del_all: yes
name:
- host01
- host02
- host03
- host04
- _ftp._tcp
- _sip._udp
state: absent
- name: Ensure that dns reverse ipv6 records are absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: ip6.arpa.
del_all: yes
name:
- 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
- 1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
- 1.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
- 4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
- 4.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
- 4.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.f
state: absent
- name: Ensure that dns reverse ipv6 records are absent (workaround)
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: "{{ zone_ipv6_reverse_workaround }}"
del_all: yes
name:
- 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
- 1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
- 1.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
- 4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
- 4.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
- 4.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0
state: absent
- name: Ensure that dns reverse records are absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: "{{ zone_prefix_reverse_24 }}"
name:
- "101"
- "102"
- "103"
- "104"
- "111"
- "112"
- "113"
- "114"
- "121"
- "122"
- "123"
- "124"
del_all: yes
state: absent
- name: Ensure that dns reverse records are absent (workaround 1)
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: "{{ zone_prefix_reverse_16 }}"
name:
- "101.122"
- "102.122"
- "103.122"
- "104.122"
- "111.122"
- "112.122"
- "113.122"
- "114.122"
- "121.122"
- "122.122"
- "123.122"
- "124.122"
del_all: yes
state: absent
- name: Ensure that dns reverse records are absent (workaround 2)
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: "{{ zone_prefix_reverse_8 }}"
name:
- "168.101.122"
- "168.102.122"
- "168.103.122"
- "168.104.122"
- "168.111.122"
- "168.112.122"
- "168.113.122"
- "168.114.122"
- "168.121.122"
- "168.122.122"
- "168.123.122"
- "168.124.122"
del_all: yes
state: absent
- name: Ensure that "{{ safezone }}" dns records are absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
zone_name: "{{ safezone }}"
records:
- name: iron01
del_all: yes
state: absent
- name: Ensure that NS record for "{{ safezone }}" is absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: iron01
zone_name: "{{ safezone }}"
ns_rec: iron01
state: absent
- name: Ensure DNS testing zones are absent.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: "{{ item }}"
state: absent
with_items:
- "{{ zone_prefix_reverse }}"
- "{{ zone_prefix_reverse_24 }}"
- "{{ zone_prefix_reverse_16 }}"
- "{{ zone_prefix_reverse_8 }}"
- "{{ testzone }}"
- ip6.arpa.
- d.f.ip6.arpa.
- "{{ safezone }}"

View File

@@ -0,0 +1,31 @@
---
- name: Setup variables and facts.
include_tasks: env_vars.yml
# Cleanup before setup.
- name: Cleanup test environment.
include_tasks: env_cleanup.yml
# Common setup tasks.
- name: Ensure DNS testing zones are present.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: "{{ item }}"
skip_nameserver_check: yes
skip_overlap_check: yes
with_items:
- "{{ zone_prefix_reverse }}"
- "{{ zone_prefix_reverse_24 }}"
- "{{ zone_prefix_reverse_16 }}"
- "{{ zone_prefix_reverse_8 }}"
- "{{ testzone }}"
- ip6.arpa.
- name: Ensure DNSSEC zone '"{{ safezone }}"' is present.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: "{{ safezone }}"
dnssec: yes
skip_nameserver_check: yes
skip_overlap_check: yes
ignore_errors: yes

View File

@@ -0,0 +1,17 @@
---
# Set common vars and facts for test.
- name: Set IPv4 address prefix.
set_fact:
ipv4_prefix: '192.168.122'
ipv4_reverse_sufix: '122.168.192'
- name: Set zone prefixes.
set_fact:
testzone: 'testzone.test'
safezone: 'safezone.test'
zone_ipv6_reverse: "ip6.arpa."
zone_ipv6_reverse_workaround: "d.f.ip6.arpa."
zone_prefix_reverse: "in-addr.arpa"
zone_prefix_reverse_24: "{{ ipv4_prefix.split('.')[::-1] | join ('.') }}.in-addr.arpa"
zone_prefix_reverse_16: "{{ ipv4_prefix.split('.')[1::-1] | join ('.') }}.in-addr.arpa"
zone_prefix_reverse_8: "{{ ipv4_prefix.split('.')[2::-1] | join ('.') }}.in-addr.arpa"

View File

@@ -0,0 +1,234 @@
---
- name: Test compatibility with Ansible ipa_dnsrecord module.
hosts: ipaserver
become: true
gather_facts: false
tasks:
# setup
- name: Ensure DNS zones to be used are absent.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: "{{ item }}"
state: absent
with_items:
- testzone.local
- 2.168.192.in-addr.arpa
- name: Ensure DNS zones to be used are present.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: "{{ item }}"
with_items:
- testzone.local
- 2.168.192.in-addr.arpa
- name: Ensure that dns record 'host01' is absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: testzone.local
record_type: 'AAAA'
record_value: '::1'
state: absent
- name: Ensure that dns record 'vm-001' is absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: vm-001
zone_name: testzone.local
record_type: 'AAAA'
record_value: '::1'
state: absent
- name: Ensure a PTR record is absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: 5
record_type: 'PTR'
record_value: 'internal.ipa.testzone.local'
zone_name: 2.168.192.in-addr.arpa
state: absent
- name: Ensure a TXT record is absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: _kerberos
record_type: 'TXT'
record_value: 'TESTZONE.LOCAL'
zone_name: testzone.local
state: absent
- name: Ensure a SRV record is absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: _kerberos._udp.testzone.local
record_type: 'SRV'
record_value: '10 50 88 ipa.testzone.local'
zone_name: testzone.local
state: absent
- name: Ensure an MX record is absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: '@'
record_type: 'MX'
record_value: '1 mailserver.testzone.local'
zone_name: testzone.local
state: absent
# tests
- name: Ensure dns record is present
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: vm-001
record_type: 'AAAA'
record_value: '::1'
zone_name: testzone.local
state: present
register: result
failed_when: not result.changed
- name: Ensure that dns record exists with a TTL
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
record_type: 'AAAA'
record_value: '::1'
record_ttl: 300
zone_name: testzone.local
state: present
register: result
failed_when: not result.changed
- name: Ensure a PTR record is present
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: 5
record_type: 'PTR'
record_value: 'internal.ipa.testzone.local'
zone_name: 2.168.192.in-addr.arpa
state: present
register: result
failed_when: not result.changed
- name: Ensure a TXT record is present
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: _kerberos
record_type: 'TXT'
record_value: 'TESTZONE.LOCAL'
zone_name: testzone.local
state: present
register: result
failed_when: not result.changed
- name: Ensure a SRV record is present
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: _kerberos._udp.testzone.local
record_type: 'SRV'
record_value: '10 50 88 ipa.testzone.local'
zone_name: testzone.local
state: present
register: result
failed_when: not result.changed
- name: Ensure an MX record is present
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: '@'
record_type: 'MX'
record_value: '1 mailserver.testzone.local'
zone_name: testzone.local
state: present
register: result
failed_when: not result.changed
- name: Ensure that dns record is removed
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: testzone.local
record_type: 'AAAA'
record_value: '::1'
state: absent
register: result
failed_when: not result.changed
# cleanup
- name: Ensure that dns record 'host01' is absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: testzone.local
record_type: 'AAAA'
record_value: '::1'
state: absent
register: result
failed_when: result.changed
- name: Ensure that dns record 'vm-001' is absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: vm-001
zone_name: testzone.local
record_type: 'AAAA'
record_value: '::1'
state: absent
register: result
failed_when: not result.changed
- name: Ensure a PTR record is absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: 5
record_type: 'PTR'
record_value: 'internal.ipa.testzone.local'
zone_name: 2.168.192.in-addr.arpa
state: absent
register: result
failed_when: not result.changed
- name: Ensure a TXT record is absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: _kerberos
record_type: 'TXT'
record_value: 'TESTZONE.LOCAL'
zone_name: testzone.local
state: absent
register: result
failed_when: not result.changed
- name: Ensure a SRV record is absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: _kerberos._udp.testzone.local
record_type: 'SRV'
record_value: '10 50 88 ipa.testzone.local'
zone_name: testzone.local
state: absent
register: result
failed_when: not result.changed
- name: Ensure an MX record is absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: '@'
record_type: 'MX'
record_value: '1 mailserver.testzone.local'
zone_name: testzone.local
state: absent
register: result
failed_when: not result.changed
- name: Ensure DNS zones to be used are absent.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: "{{ item }}"
state: absent
with_items:
- testzone.local
- 2.168.192.in-addr.arpa

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,150 @@
---
- name: Test dnsrecord with full records (*-rec variables).
hosts: ipaserver
become: yes
gather_facts: yes
tasks:
- name: Setup test environment
include_tasks: env_setup.yml
# tests
- name: Ensure that dns A record for 'host01' is present
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: "{{ testzone }}"
a_rec: 192.168.122.101
register: result
failed_when: not result.changed
- name: Ensure that dns A record for 'host01' is present, again
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: "{{ testzone }}"
a_rec: 192.168.122.101
register: result
failed_when: result.changed
- name: Ensure that dns A records for 'host01' are present
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: "{{ testzone }}"
a_rec:
- 192.168.122.101
- 192.168.122.102
- 192.168.122.103
register: result
failed_when: not result.changed
- name: Ensure that dns A records for 'host01' are present, again
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: "{{ testzone }}"
a_rec:
- 192.168.122.101
- 192.168.122.102
- 192.168.122.103
register: result
failed_when: result.changed
- name: Ensure that dns A records for 'host01' are absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: "{{ testzone }}"
a_rec:
- 192.168.122.101
- 192.168.122.102
state: absent
register: result
failed_when: not result.changed
- name: Ensure that dns A records for 'host01' are absent, again
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: "{{ testzone }}"
a_rec:
- 192.168.122.101
- 192.168.122.102
state: absent
register: result
failed_when: result.changed
####
- name: Ensure that dns AAAA record for 'host01' is present
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: "{{ testzone }}"
aaaa_rec: fd00::0001
register: result
failed_when: not result.changed
- name: Ensure that dns AAAA record for 'host01' is present, again
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: "{{ testzone }}"
aaaa_rec: fd00::0001
register: result
failed_when: result.changed
- name: Ensure that dns AAAA records for 'host01' are present
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: "{{ testzone }}"
aaaa_rec:
- fd00::0001
- fd00::0011
- fd00::0021
register: result
failed_when: not result.changed
- name: Ensure that dns AAAAA records for 'host01' are present, again
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: "{{ testzone }}"
aaaa_rec:
- fd00::0001
- fd00::0011
- fd00::0021
register: result
failed_when: result.changed
- name: Ensure that dns AAAAA records for 'host01' are absent
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: "{{ testzone }}"
aaaa_rec:
- fd00::0001
- fd00::0011
state: absent
register: result
failed_when: not result.changed
- name: Ensure that dns AAAAA records for 'host01' are absent, again
ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: "{{ testzone }}"
aaaa_rec:
- fd00::0001
- fd00::0011
state: absent
register: result
failed_when: result.changed
# Cleanup
- name: Cleanup test environment.
include_tasks: env_cleanup.yml

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