Compare commits

...

130 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
7897bd4d8e Merge pull request #192 from jesmg/patch-1
Not delete keytab when ipaclient_on_master is true
2020-04-22 13:55:37 +02: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
Rafael Guterres Jeffman
4ba34077f9 Merge pull request #243 from t-woerner/galaxy-fix
Galaxy fix
2020-04-06 20:44:21 -03:00
Thomas Woerner
3a37325a36 galaxyfy-playbook.py: Fixed script name
The old name was galaxyify-playbook.py instead of galaxyfy-playbook.py
2020-04-02 14:46:54 +02:00
Thomas Woerner
57d407f15f utils/*galaxy*: Make galaxy scripts more generic
The namespace and colleciton name have been hard coded. Now variables are
used for them. The project prefix and collection prefix are now passed to
galaxyify-playbook.py.
2020-04-02 11:26:32 +02:00
Thomas Woerner
cd5429a534 ipareplica_setup_krb: krb is assigned to but never used
krb was set, but not used afterwards. Therefore it can be removed.
2020-04-02 10:50:41 +02:00
Thomas Woerner
ffd8585d19 ipareplica_setup_kra: Remove unused ccache parameter
The installer_ccache parameter is used in the module. The ccache parameter
was only set, but not used at all.
2020-04-02 10:48:53 +02:00
Sergio Oliveira
2897267440 Merge pull request #217 from rjeffman/sudorule_test_enhancement
Sudorule test enhancement
2020-03-30 17:35:08 -03:00
Thomas Woerner
2712e39bc4 galaxy.yml: Add system tag 2020-03-30 16:14:12 +02:00
Thomas Woerner
a972beb484 ipaserver docs: Calm down module linter
The use of "default: idstart+199999" in the description of the idmax
parameter was resulting in the galaxy import error:

  Cannot parse "DOCUMENTATION": mapping values are not allowed here in
  "<unicode string>", line 52, column 58: ... value for the IDs range
  (default: idstart+199999)

The ":" has simply been removed to fix this issue.
2020-03-30 15:01:55 +02:00
Thomas Woerner
50a1c2f9cd utils/build-galaxy-release: Do not add release tag to version for galaxy
Galaxy does not like the use of the extra "-1" release tag.

Fixes: #236 (Can't install via Galaxy)
2020-03-30 14:45:02 +02:00
Rafael Guterres Jeffman
0fb05dfaca Merge pull request #240 from seocam/dnszone-update
Fixed a bug in AnsibleFreeIPAParams
2020-03-26 14:03:02 -03:00
Sergio Oliveira Campos
2205907220 Fixed a bug in AnsibleFreeIPAParams
When accessing an instance of AnsibleFreeIPAParams with .get the obj was
by-passing the call to _afm_convert which was the primaty reason why it
was created.

Also the class now extends Mapping instead of dict.
2020-03-26 13:10:54 -03:00
Rafael Guterres Jeffman
d7af454d77 Merge pull request #239 from seocam/dnszone-update
Added aliases for in dnszone module arguments
2020-03-26 09:22:13 -03:00
Sergio Oliveira Campos
35d7658834 Added alias module arguments in dnszone module 2020-03-26 09:15:23 -03:00
Sergio Oliveira
aeaeaadd27 Merge pull request #238 from rjeffman/fix_dnsconfig_passwd
Add admin password to the ipadnsconfig module tests.
2020-03-25 17:51:20 -03:00
Rafael Guterres Jeffman
abe2605a55 Add admin password to the ipadnsconfig module tests.
This change avoid the need to obtain an admin TGT on the testing target before running the tests.
2020-03-25 17:42:24 -03:00
Rafael Guterres Jeffman
492a2bf39e Merge pull request #231 from Akasurde/i115
Handle RuntimeError in fail_json
2020-03-25 11:47:33 -03:00
Rafael Guterres Jeffman
4ab38e8bc6 Merge pull request #233 from t-woerner/setup_logging
ipa[server,replica,client]: setup_logging wrapper for standard_logging setup
2020-03-25 11:39:23 -03:00
Rafael Guterres Jeffman
3400f9556b Merge pull request #224 from seocam/dnszone
DNSZone module
2020-03-24 15:28:29 -03:00
Sergio Oliveira Campos
2ed7e21c1f New IPADNSZone module
There is a new management module placed in the plugins folder:

    plugins/modules/ipadnszone.py

    The dnszone module allows to manage DNS zones.

    Here is the documentation for the module:

    README-dnszone.md

    New example playbooks have been added:

    playbooks/dnszone/disable-zone-forwarders.yml
    playbooks/dnszone/dnszone-absent.yml
    playbooks/dnszone/dnszone-all-params.yml
    playbooks/dnszone/dnszone-disable.yml
    playbooks/dnszone/dnszone-enable.yml
    playbooks/dnszone/dnszone-present.yml

    New tests for the module:

    tests/dnszone/test_dnszone.yml
    tests/dnszone/test_dnszone_mod.yml
2020-03-24 10:52:53 -03:00
Sergio Oliveira Campos
e76047edb0 Created FreeIPABaseModule class to facilitate creation of new modules 2020-03-24 10:40:04 -03:00
Sergio Oliveira
b211b50b2d Merge pull request #232 from t-woerner/ipareplica_prepare_docs
ipareplica_prepare: Fix module DOCUMENTATION
2020-03-20 17:17:55 -03:00
Thomas Woerner
d31a132a59 ipa[server,replica,client]: setup_logging wrapper for standard_logging_setup
The import of ansible_ipa_server, ansible_ipa_replica and ansible_ipa_client
might result in a permission denied error for the log file. It seems that
for collections the module utils seem to be loaded before the needed
permissions are aquired now.

The fix simply adds a wrapper for standard_logging_setup that is called in
all the modules of the server, replica and client roles to do the loggin
setup as one of the first steps of the module execution and not before.
2020-03-20 13:55:42 +01:00
Thomas Woerner
7576732525 ipareplica_prepare: Fix module DOCUMENTATION
The documentation contains the pramaters several times. Reducing the list
to one. Also fixed a typo in options key.
2020-03-20 13:53:46 +01:00
Abhijeet Kasurde
cfdf2896ba Handle RuntimeError in fail_json
Gracefully handle RuntimeError raised during parameter validation
in fail_json.

Fixes: #115

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2020-03-20 16:57:20 +05:30
Rafael Guterres Jeffman
8c2268a560 Enhance sudorule module tests.
This patch adds tests for some options that were not being tested, and
enhances test behavior.
2020-03-18 10:52:35 -03:00
Thomas Woerner
81179b709b Merge pull request #163 from chr15p/master
Add dnsforwardzone module
2020-03-16 17:49:04 +01:00
Thomas Woerner
d33935583c Merge branch 'master' into master 2020-03-16 17:47:57 +01:00
chrisp
708675d9c2 add a module to manage dns forwarder zones in ipa 2020-03-10 16:14:54 +00:00
Jesús
7cf80c59b8 Not delete keytab when ipaclient_on_master is true
Keep the valid keytab file pre-existent in the master node. This fixes #191.
2020-01-23 18:09:10 +01:00
182 changed files with 11806 additions and 2389 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

112
README-dnsforwardzone.md Normal file
View File

@@ -0,0 +1,112 @@
Dnsforwardzone module
=====================
Description
-----------
The dnsforwardzone module allows the addition and removal of dns forwarders from the IPA DNS config.
It is desgined to follow the IPA api as closely as possible while ensuring ease of use.
Features
--------
* DNS zone management
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipadnsforwardzone module.
Requirements
------------
**Controller**
* Ansible version: 2.8+
**Node**
* Supported FreeIPA version (see above)
Usage
=====
Example inventory file
```ini
[ipaserver]
ipaserver.test.local
```
Example playbook to ensure presence of a forwardzone to ipa DNS:
```yaml
---
- name: Playbook to handle add a forwarder
hosts: ipaserver
become: true
tasks:
- name: ensure presence of forwardzone for DNS requests for example.com to 8.8.8.8
ipadnsforwardzone:
ipaadmin_password: password01
state: present
name: example.com
forwarders:
- 8.8.8.8
forwardpolicy: first
skip_overlap_check: true
- name: ensure the forward zone is disabled
ipadnsforwardzone:
ipaadmin_password: password01
name: example.com
state: disabled
- name: ensure presence of multiple upstream DNS servers for example.com
ipadnsforwardzone:
ipaadmin_password: password01
state: present
name: example.com
forwarders:
- 8.8.8.8
- 4.4.4.4
- name: ensure presence of another forwarder to any existing ones for example.com
ipadnsforwardzone:
ipaadmin_password: password01
state: present
name: example.com
forwarders:
- 1.1.1.1
action: member
- name: ensure the forwarder for example.com does not exists (delete it if needed)
ipadnsforwardzone:
ipaadmin_password: password01
name: example.com
state: absent
```
Variables
=========
ipagroup
-------
Variable | Description | Required
-------- | ----------- | --------
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
`name` \| `cn` | Zone name (FQDN). | yes if `state` == `present`
`forwarders` \| `idnsforwarders` | Per-zone conditional forwarding policy. Possible values are `only`, `first`, `none`) | no
`forwardpolicy` \| `idnsforwardpolicy` | Per-zone conditional forwarding policy. Set to "none" to disable forwarding to global forwarder for this zone. In that case, conditional zone forwarders are disregarded. | no
`skip_overlap_check` | Force DNS zone creation even if it will overlap with an existing zone. Defaults to False. | no
`action` | Work on group or member level. It can be on of `member` or `dnsforwardzone` and defaults to `dnsforwardzone`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, `enabled` or `disabled`, default: `present`. | yes
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

195
README-dnszone.md Normal file
View File

@@ -0,0 +1,195 @@
DNSZone Module
==============
Description
-----------
The dnszone module allows to configure zones in DNS server.
Features
--------
* Add, remove, modify, enable or disable DNS zones.
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by ipadnszone module.
Requirements
------------
**Controller**
* Ansible version: 2.8+
**Node**
* Supported FreeIPA version (see above)
Usage
-----
```ini
[ipaserver]
ipaserver.test.local
```
Example playbook to create a simple DNS zone:
```yaml
---
- name: dnszone present
hosts: ipaserver
become: true
tasks:
- name: Ensure zone is present.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: testzone.local
state: present
```
Example playbook to create a DNS zone with all currently supported variables:
```yaml
---
- name: dnszone present
hosts: ipaserver
become: true
tasks:
- name: Ensure zone is present.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: testzone.local
allow_sync_ptr: true
dynamic_update: true
dnssec: true
allow_transfer:
- 1.1.1.1
- 2.2.2.2
allow_query:
- 1.1.1.1
- 2.2.2.2
forwarders:
- ip_address: 8.8.8.8
- ip_address: 8.8.4.4
port: 52
serial: 1234
refresh: 3600
retry: 900
expire: 1209600
minimum: 3600
ttl: 60
default_ttl: 90
name_server: ipaserver.test.local.
admin_email: admin.admin@example.com
nsec3param_rec: "1 7 100 0123456789abcdef"
skip_overlap_check: true
skip_nameserver_check: true
state: present
```
Example playbook to disable a zone:
```yaml
---
- name: Playbook to disable DNS zone
hosts: ipaserver
become: true
tasks:
- name: Disable zone.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: testzone.local
state: disabled
```
Example playbook to enable a zone:
```yaml
---
- name: Playbook to enable DNS zone
hosts: ipaserver
become: true
tasks:
- name: Enable zone.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: testzone.local
state: enabled
```
Example playbook to remove a zone:
```yaml
---
- name: Playbook to remove DNS zone
hosts: ipaserver
become: true
tasks:
- name: Remove zone.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: testzone.local
state: absent
```
Variables
=========
ipadnszone
----------
Variable | Description | Required
-------- | ----------- | --------
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
`name` \| `zone_name` | The zone name string. | yes
`forwarders` | The list of forwarders dicts. Each `forwarders` dict entry has:| no
&nbsp; | `ip_address` - The IPv4 or IPv6 address of the DNS server. | yes
&nbsp; | `port` - The custom port that should be used on this server. | no
`forward_policy` | The global forwarding policy. It can be one of `only`, `first`, or `none`. | no
`allow_sync_ptr` | Allow synchronization of forward (A, AAAA) and reverse (PTR) records (bool). | no
`state` | The state to ensure. It can be one of `present`, `enabled`, `disabled` or `absent`, default: `present`. | yes
`name_server`| Authoritative nameserver domain name | no
`admin_email`| Administrator e-mail address | no
`update_policy`| BIND update policy | no
`dynamic_update` \| `dynamicupdate` | Allow dynamic updates | no
`dnssec`| Allow inline DNSSEC signing of records in the zone | no
`allow_transfer`| List of IP addresses or networks which are allowed to transfer the zone | no
`allow_query`| List of IP addresses or networks which are allowed to issue queries | no
`serial`| SOA record serial number | no
`refresh`| SOA record refresh time | no
`retry`| SOA record retry time | no
`expire`| SOA record expire time | no
`minimum`| How long should negative responses be cached | no
`ttl`| Time to live for records at zone apex | no
`default_ttl`| Time to live for records without explicit TTL definition | no
`nsec3param_rec`| NSEC3PARAM record for zone in format: hash_algorithm flags iterations salt | no
`skip_overlap_check`| Force DNS zone creation even if it will overlap with an existing zone | no
`skip_nameserver_check` | Force DNS zone creation even if nameserver is not resolvable | no
Authors
=======
Sergio Oliveira Campos

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

@@ -11,6 +11,9 @@ Features
* Cluster deployments: Server, replicas and clients in one playbook
* 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
* Modules for hbacsvc management
@@ -408,6 +411,9 @@ 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)
* [ipahbacsvc](README-hbacsvc.md)
@@ -423,3 +429,5 @@ Modules in plugin/modules
* [ipatopologysuffix](README-topology.md)
* [ipauser](README-user.md)
* [ipavault](README-vault.md)
If you want to write a new module please read [writing a new module](plugins/modules/README.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,11 +13,11 @@ issues: "https://github.com/freeipa/ansible-freeipa/issues"
readme: "README.md"
license: "GPL-3.0-or-later"
license_file: "COPYING"
dependencies:
tags:
- "system"
- "identity"
- "ipa"
- "freeipa"

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,11 @@
---
- name: Playbook to disable DNS zone forwarders
hosts: ipaserver
become: true
tasks:
- name: Disable zone forwarders.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: testzone.local
forward_policy: none

View File

@@ -0,0 +1,11 @@
---
- name: Playbook to ensure DNS zone is absent
hosts: ipaserver
become: true
tasks:
- name: Remove zone.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: testzone.local
state: absent

View File

@@ -0,0 +1,35 @@
- name: dnszone present
hosts: ipaserver
become: true
tasks:
- name: Ensure zone is present.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: testzone.local
allow_sync_ptr: true
dynamic_update: true
dnssec: true
allow_transfer:
- 1.1.1.1
- 2.2.2.2
allow_query:
- 1.1.1.1
- 2.2.2.2
forwarders:
- ip_address: 8.8.8.8
- ip_address: 8.8.4.4
port: 52
#serial: 1234
refresh: 3600
retry: 900
expire: 1209600
minimum: 3600
ttl: 60
default_ttl: 90
name_server: ipaserver.test.local.
admin_email: admin.admin@example.com
nsec3param_rec: "1 7 100 0123456789abcdef"
skip_overlap_check: true
skip_nameserver_check: true
state: present

View File

@@ -0,0 +1,11 @@
---
- name: Playbook to disable DNS zone
hosts: ipaserver
become: true
tasks:
- name: Disable zone.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: testzone.local
state: disabled

View File

@@ -0,0 +1,11 @@
---
- name: Playbook to enable DNS zone
hosts: ipaserver
become: true
tasks:
- name: Enable zone.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: testzone.local
state: enabled

View File

@@ -0,0 +1,10 @@
- name: dnszone present
hosts: ipaserver
become: true
tasks:
- name: Ensure zone is present.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: testzone.local
state: present

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

@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# Authors:
# Sergio Oliveira Campos <seocam@redhat.com>
# Thomas Woerner <twoerner@redhat.com>
#
# Copyright (C) 2019 Red Hat
@@ -27,35 +28,50 @@ import tempfile
import shutil
import gssapi
from datetime import datetime
from pprint import pformat
from ipalib import api
from ipalib import errors as ipalib_errors
from ipalib import errors as ipalib_errors # noqa
from ipalib.config import Env
from ipalib.constants import DEFAULT_CONFIG, LDAP_GENERALIZED_TIME_FORMAT
try:
from ipalib.install.kinit import kinit_password, kinit_keytab
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
from ansible.module_utils._text import to_text
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
try:
from collections.abc import Mapping # noqa
except ImportError:
from collections import Mapping # noqa
if six.PY3:
unicode = str
def valid_creds(module, principal):
"""
Get valid credintials matching the princial, try GSSAPI first
"""
def valid_creds(module, principal): # noqa
"""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)
@@ -93,9 +109,7 @@ def valid_creds(module, principal):
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:
@@ -109,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()
@@ -143,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.
"""
@@ -205,9 +225,24 @@ def date_format(value):
raise ValueError("Invalid date '%s'" % value)
def compare_args_ipa(module, args, ipa):
def compare_args_ipa(module, args, ipa): # noqa
"""Compare IPA obj attrs with the command args.
This function compares IPA objects attributes with the args the
module is intending to use to call a command. This is useful to know
if call to IPA server will be needed or not.
In other to compare we have to prepare the perform slight changes in
data formats.
Returns True if they are the same and False otherwise.
"""
base_debug_msg = "Ansible arguments and IPA commands differed. "
for key in args.keys():
if key not in ipa:
module.debug(
base_debug_msg + "Command key not present in IPA: %s" % key
)
return False
else:
arg = args[key]
@@ -220,25 +255,35 @@ def compare_args_ipa(module, args, ipa):
if isinstance(ipa_arg, list):
if not isinstance(arg, list):
arg = [arg]
if len(ipa_arg) != len(arg):
module.debug(
base_debug_msg
+ "List length doesn't match for key %s: %d %d"
% (key, len(arg), len(ipa_arg),)
)
return False
if isinstance(ipa_arg[0], str) and isinstance(arg[0], int):
arg = [to_text(_arg) for _arg in arg]
if isinstance(ipa_arg[0], unicode) and isinstance(arg[0], int):
arg = [to_text(_arg) for _arg in arg]
# module.warn("%s <=> %s" % (repr(arg), repr(ipa_arg)))
try:
arg_set = set(arg)
ipa_arg_set = set(ipa_arg)
except TypeError:
if arg != ipa_arg:
# module.warn("%s != %s" % (repr(arg), repr(ipa_arg)))
module.debug(
base_debug_msg
+ "Different values: %s %s" % (arg, ipa_arg)
)
return False
else:
if arg_set != ipa_arg_set:
# module.warn("%s != %s" % (repr(arg), repr(ipa_arg)))
module.debug(
base_debug_msg
+ "Different set content: %s %s"
% (arg_set, ipa_arg_set,)
)
return False
# module.warn("%s == %s" % (repr(arg), repr(ipa_arg)))
return True
@@ -265,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 []))
@@ -277,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)
@@ -289,10 +336,42 @@ 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
if 1 <= port <= 65535:
return True
return False
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:
@@ -301,11 +380,309 @@ 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:
return False
return True
class AnsibleFreeIPAParams(Mapping):
def __init__(self, ansible_module):
self.mapping = ansible_module.params
self.ansible_module = ansible_module
def __getitem__(self, key):
param = self.mapping[key]
if param is not None:
return _afm_convert(param)
def __iter__(self):
return iter(self.mapping)
def __len__(self):
return len(self.mapping)
@property
def names(self):
return self.name
def __getattr__(self, name):
return self.get(name)
class FreeIPABaseModule(AnsibleModule):
"""
Base class for FreeIPA Ansible modules.
Provides methods useful methods to be used by our modules.
This class should be overriten and instantiated for the module.
A basic implementation of an Ansible FreeIPA module expects its
class to:
1. Define a class attribute ``ipa_param_mapping``
2. Implement the method ``define_ipa_commands()``
3. Implement the method ``check_ipa_params()`` (optional)
After instantiating the class the method ``ipa_run()`` should be called.
Example (ansible-freeipa/plugins/modules/ipasomemodule.py):
class SomeIPAModule(FreeIPABaseModule):
ipa_param_mapping = {
"arg_to_be_passed_to_ipa_command": "module_param",
"another_arg": "get_another_module_param",
}
def get_another_module_param(self):
another_module_param = self.ipa_params.another_module_param
# Validate or modify another_module_param
# ...
return another_module_param
def check_ipa_params(self):
# Validate your params here
# Example:
if not self.ipa_params.module_param in VALID_OPTIONS:
self.fail_json(msg="Invalid value for argument module_param")
def define_ipa_commands(self):
args = self.get_ipa_command_args()
self.add_ipa_command(
"some_ipa_command",
name="obj-name",
args=args,
)
def main():
ipa_module = SomeIPAModule(argument_spec=dict(
module_param=dict(
type="str",
default=None,
required=False,
),
another_module_param=dict(
type="str",
default=None,
required=False,
),
))
ipa_module.ipa_run()
if __name__ == "__main__":
main()
"""
ipa_param_mapping = None
def __init__(self, *args, **kwargs):
super(FreeIPABaseModule, self).__init__(*args, **kwargs)
# Attributes to store kerberos credentials (if needed)
self.ccache_dir = None
self.ccache_name = None
# Status of an execution. Will be changed to True
# if something is actually peformed.
self.changed = False
# Status of the connection with the IPA server.
# We need to know if the connection was actually stablished
# before we start sending commands.
self.ipa_connected = False
# Commands to be executed
self.ipa_commands = []
# Module exit arguments.
self.exit_args = {}
# Wrapper around the AnsibleModule.params.
# Return the actual params but performing transformations
# when needed.
self.ipa_params = AnsibleFreeIPAParams(self)
def get_ipa_command_args(self):
"""
Return a dict to be passed to an IPA command.
The keys of ``ipa_param_mapping`` are also the keys of the return dict.
The values of ``ipa_param_mapping`` needs to be either:
* A str with the name of a defined method; or
* A key of ``AnsibleModule.param``.
In case of a method the return of the method will be set as value
for the return dict.
In case of a AnsibleModule.param the value of the param will be
set in the return dict. In addition to that boolean values will be
automaticaly converted to uppercase strings (as required by FreeIPA
server).
"""
args = {}
for ipa_param_name, param_name in self.ipa_param_mapping.items():
# Check if param_name is actually a param
if param_name in self.ipa_params:
value = self.ipa_params.get(param_name)
if isinstance(value, bool):
value = "TRUE" if value else "FALSE"
# Since param wasn't a param check if it's a method name
elif hasattr(self, param_name):
method = getattr(self, param_name)
if callable(method):
value = method()
# We don't have a way to guess the value so fail.
else:
self.fail_json(
msg=(
"Couldn't get a value for '%s'. Option '%s' is not "
"a module argument neither a defined method."
)
% (ipa_param_name, param_name)
)
if value is not None:
args[ipa_param_name] = value
return args
def check_ipa_params(self):
"""Validate ipa_params before command is called."""
pass
def define_ipa_commands(self):
"""Define commands that will be run in IPA server."""
raise NotImplementedError
def api_command(self, command, name=None, args=None):
"""Execute a single command in IPA server."""
if args is None:
args = {}
if name is None:
return api_command_no_name(self, command, args)
return api_command(self, command, name, args)
def __enter__(self):
"""
Connect to IPA server.
Check the there are working Kerberos credentials to connect to
IPA server. If there are not we perform a temporary kinit
that will be terminated when exiting the context.
If the connection fails ``ipa_connected`` attribute will be set
to False.
"""
principal = self.ipa_params.ipaadmin_principal
password = self.ipa_params.ipaadmin_password
try:
if not valid_creds(self, principal):
self.ccache_dir, self.ccache_name = temp_kinit(
principal, password,
)
api_connect()
except Exception as excpt:
self.fail_json(msg=str(excpt))
else:
self.ipa_connected = True
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""
Terminate a connection with the IPA server.
Deal with exceptions, destroy temporary kinit credentials and
exit the module with proper arguments.
"""
if exc_val:
self.fail_json(msg=str(exc_val))
# TODO: shouldn't we also disconnect from api backend?
temp_kdestroy(self.ccache_dir, self.ccache_name)
self.exit_json(changed=self.changed, user=self.exit_args)
def get_command_errors(self, command, result):
"""Look for erros into command results."""
# Get all errors
# All "already a member" and "not a member" failures in the
# result are ignored. All others are reported.
errors = []
for item in result.get("failed", tuple()):
failed_item = result["failed"][item]
for member_type in failed_item:
for member, failure in failed_item[member_type]:
if (
"already a member" in failure
or "not a member" in failure
):
continue
errors.append(
"%s: %s %s: %s"
% (command, member_type, member, failure)
)
if len(errors) > 0:
self.fail_json(", ".join("errors"))
def add_ipa_command(self, command, name=None, args=None):
"""Add a command to the list of commands to be executed."""
self.ipa_commands.append((name, command, args or {}))
def _run_ipa_commands(self):
"""Execute commands in self.ipa_commands."""
result = None
for name, command, args in self.ipa_commands:
try:
result = self.api_command(command, name, args)
except Exception as excpt:
self.fail_json(msg="%s: %s: %s" % (command, name, str(excpt)))
else:
if "completed" in result:
if result["completed"] > 0:
self.changed = True
else:
self.changed = True
self.get_command_errors(command, result)
def require_ipa_attrs_change(self, command_args, ipa_attrs):
"""
Compare given args with current object attributes.
Returns True in case current IPA object attributes differ from
args passed to the module.
"""
equal = compare_args_ipa(self, command_args, ipa_attrs)
return not equal
def pdebug(self, value):
"""Debug with pretty formatting."""
self.debug(pformat(value))
def ipa_run(self):
"""Execute module actions."""
with self:
if not self.ipa_connected:
return
self.check_ipa_params()
self.define_ipa_commands()
self._run_ipa_commands()

80
plugins/modules/README.md Normal file
View File

@@ -0,0 +1,80 @@
# Writing a new Ansible FreeIPA module
## Minimum requirements
A ansible-freeipa module should have:
* Code:
* A module file placed in `plugins/modules/<ipa_module_name>.py`
* Documentation:
* `README-<module_name>.md` file in the root directory and linked from the main README.md
* Example playbooks in `playbooks/<module_name>/` directory
* Tests:
* Test cases (also playbooks) defined in `tests/<module_name>/test_<something>.yml`. It's ok to have multiple files in this directory.
## Code
The module file have to start with the python shebang line, license header and definition of the constants `ANSIBLE_METADATA`, `DOCUMENTATION`, `EXAMPLES` and `RETURNS`. Those constants need to be defined before the code (even imports). See https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html#starting-a-new-module for more information.
Although it's use is not yet required, ansible-freeipa provides `FreeIPABaseModule` as a helper class for the implementation of new modules. See the example bellow:
```python
from ansible.module_utils.ansible_freeipa_module import FreeIPABaseModule
class SomeIPAModule(FreeIPABaseModule):
ipa_param_mapping = {
"arg_to_be_passed_to_ipa_command": "module_param",
"another_arg": "get_another_module_param",
}
def get_another_module_param(self):
another_module_param = self.ipa_params.another_module_param
# Validate or modify another_module_param ...
return another_module_param
def check_ipa_params(self):
# Validate your params here ...
# Example:
if not self.ipa_params.module_param in VALID_OPTIONS:
self.fail_json(msg="Invalid value for argument module_param")
def define_ipa_commands(self):
args = self.get_ipa_command_args()
self.add_ipa_command("some_ipa_command", name="obj-name", args=args)
def main():
ipa_module = SomeIPAModule(argument_spec=dict(
module_param=dict(type="str", default=None, required=False),
another_module_param=dict(type="str", default=None, required=False),
))
ipa_module.ipa_run()
if __name__ == "__main__":
main()
```
In the example above, the module will call the command `some_ipa_command`, using "obj-name" as name and, `arg_to_be_passed_to_ipa_command` and `another_arg` as arguments.
The values of the arguments will be determined by the class attribute `ipa_param_mapping`.
In the case of `arg_to_be_passed_to_ipa_command` the key (`module_param`) is defined in the module `argument_specs` so the value of the argument is actually used.
On the other hand, `another_arg` as mapped to something else: a callable method. In this case the method will be called and it's result used as value for `another_arg`.
**NOTE**: Keep mind that to take advantage of the parameters mapping defined in `ipa_param_mapping` you will have to call `args = self.get_ipa_command_args()` and use `args` in your command. There is no implicit call of this method.
## Disclaimer
The `FreeIPABaseModule` is new and might not be suitable to all cases and every module yet. In case you need to extend it's functionality for a new module please open an issue or PR and we'll be happy to discuss it.

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

View File

@@ -0,0 +1,316 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Authors:
# Chris Procter <cprocter@redhat.com>
#
# Copyright (C) 2019 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_dnsforwardzone
author: chris procter
short_description: Manage FreeIPA DNS Forwarder Zones
description:
- Add and delete an IPA DNS Forwarder Zones using IPA API
options:
ipaadmin_principal:
description: The admin principal
default: admin
ipaadmin_password:
description: The admin password
required: false
name:
description:
- The DNS zone name which needs to be managed.
required: true
aliases: ["cn"]
state:
description: State to ensure
required: false
default: present
choices: ["present", "absent", "enabled", "disabled"]
forwarders:
description:
- List of the DNS servers to forward to
required: true
type: list
aliases: ["idnsforwarders"]
forwardpolicy:
description: Per-zone conditional forwarding policy
required: false
default: only
choices: ["only", "first", "none"]
aliases: ["idnsforwarders"]
skip_overlap_check:
description:
- Force DNS zone creation even if it will overlap with an existing zone.
required: false
default: false
'''
EXAMPLES = '''
# Ensure dns zone is present
- ipadnsforwardzone:
ipaadmin_password: MyPassword123
state: present
name: example.com
forwarders:
- 8.8.8.8
- 4.4.4.4
forwardpolicy: first
skip_overlap_check: true
# Ensure that dns zone is removed
- ipadnsforwardzone:
ipaadmin_password: MyPassword123
name: example.com
state: absent
'''
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
def find_dnsforwardzone(module, name):
_args = {
"all": True,
"idnsname": name
}
_result = api_command(module, "dnsforwardzone_find", name, _args)
if len(_result["result"]) > 1:
module.fail_json(
msg="There is more than one dnsforwardzone '%s'" % (name))
elif len(_result["result"]) == 1:
return _result["result"][0]
else:
return None
def gen_args(forwarders, forwardpolicy, skip_overlap_check):
_args = {}
if forwarders is not None:
_args["idnsforwarders"] = forwarders
if forwardpolicy is not None:
_args["idnsforwardpolicy"] = forwardpolicy
if skip_overlap_check is not None:
_args["skip_overlap_check"] = skip_overlap_check
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),
name=dict(type="str", aliases=["cn"], default=None,
required=True),
forwarders=dict(type='list', aliases=["idnsforwarders"],
required=False),
forwardpolicy=dict(type='str', aliases=["idnsforwardpolicy"],
required=False,
choices=['only', 'first', 'none']),
skip_overlap_check=dict(type='bool', required=False),
action=dict(type="str", default="dnsforwardzone",
choices=["member", "dnsforwardzone"]),
# state
state=dict(type='str', default='present',
choices=['present', 'absent', 'enabled', 'disabled']),
),
supports_check_mode=True,
)
ansible_module._ansible_debug = True
# Get parameters
ipaadmin_principal = module_params_get(ansible_module,
"ipaadmin_principal")
ipaadmin_password = module_params_get(ansible_module,
"ipaadmin_password")
name = module_params_get(ansible_module, "name")
action = module_params_get(ansible_module, "action")
forwarders = module_params_get(ansible_module, "forwarders")
forwardpolicy = module_params_get(ansible_module, "forwardpolicy")
skip_overlap_check = module_params_get(ansible_module,
"skip_overlap_check")
state = module_params_get(ansible_module, "state")
# absent stae means delete if the action is NOT member but update if it is
# if action is member then update an exisiting resource
# and if action is not member then create a resource
if state == "absent" and action == "dnsforwardzone":
operation = "del"
elif action == "member":
operation = "update"
else:
operation = "add"
if state == "disabled":
wants_enable = False
else:
wants_enable = True
if operation == "del":
invalid = ["forwarders", "forwardpolicy", "skip_overlap_check"]
for x in invalid:
if vars()[x] is not None:
ansible_module.fail_json(
msg="Argument '%s' can not be used with action "
"'%s'" % (x, action))
changed = False
exit_args = {}
args = {}
ccache_dir = None
ccache_name = None
is_enabled = "IGNORE"
try:
# we need to determine 3 variables
# args = the values we want to change/set
# command = the ipa api command to call del, add, or mod
# is_enabled = is the current resource enabled (True)
# disabled (False) and do we care (IGNORE)
if not valid_creds(ansible_module, ipaadmin_principal):
ccache_dir, ccache_name = temp_kinit(ipaadmin_principal,
ipaadmin_password)
api_connect()
# Make sure forwardzone exists
existing_resource = find_dnsforwardzone(ansible_module, name)
if existing_resource is None and operation == "update":
# does not exist and is updating
# trying to update something that doesn't exist, so error
ansible_module.fail_json(msg="""dnsforwardzone '%s' is not
valid""" % (name))
elif existing_resource is None and operation == "del":
# does not exists and should be absent
# set command
command = None
# enabled or disabled?
is_enabled = "IGNORE"
elif existing_resource is not None and operation == "del":
# exists but should be absent
# set command
command = "dnsforwardzone_del"
# enabled or disabled?
is_enabled = "IGNORE"
elif forwarders is None:
# forwarders are not defined its not a delete, update state?
# set command
command = None
# enabled or disabled?
if existing_resource is not None:
is_enabled = existing_resource["idnszoneactive"][0]
else:
is_enabled = "IGNORE"
elif existing_resource is not None and operation == "update":
# exists and is updating
# calculate the new forwarders and mod
# determine args
if state != "absent":
forwarders = list(set(existing_resource["idnsforwarders"]
+ forwarders))
else:
forwarders = list(set(existing_resource["idnsforwarders"])
- set(forwarders))
args = gen_args(forwarders, forwardpolicy,
skip_overlap_check)
if skip_overlap_check is not None:
del args['skip_overlap_check']
# command
if not compare_args_ipa(ansible_module, args, existing_resource):
command = "dnsforwardzone_mod"
else:
command = None
# enabled or disabled?
is_enabled = existing_resource["idnszoneactive"][0]
elif existing_resource is None and operation == "add":
# does not exist but should be present
# determine args
args = gen_args(forwarders, forwardpolicy,
skip_overlap_check)
# set command
command = "dnsforwardzone_add"
# enabled or disabled?
is_enabled = "TRUE"
elif existing_resource is not None and operation == "add":
# exists and should be present, has it changed?
# determine args
args = gen_args(forwarders, forwardpolicy, skip_overlap_check)
if skip_overlap_check is not None:
del args['skip_overlap_check']
# set command
if not compare_args_ipa(ansible_module, args, existing_resource):
command = "dnsforwardzone_mod"
else:
command = None
# enabled or disabled?
is_enabled = existing_resource["idnszoneactive"][0]
# if command is set then run it with the args
if command is not None:
api_command(ansible_module, command, name, args)
changed = True
# does the enabled state match what we want (if we care)
if is_enabled != "IGNORE":
if wants_enable and is_enabled != "TRUE":
api_command(ansible_module, "dnsforwardzone_enable",
name, {})
changed = True
elif not wants_enable and is_enabled != "FALSE":
api_command(ansible_module, "dnsforwardzone_disable",
name, {})
changed = True
except Exception as e:
ansible_module.fail_json(msg=str(e))
finally:
temp_kdestroy(ccache_dir, ccache_name)
# Done
ansible_module.exit_json(changed=changed, dnsforwardzone=exit_args)
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,474 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Authors:
# Sergio Oliveira Campos <seocam@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: ipadnszone
short description: Manage FreeIPA dnszone
description: Manage FreeIPA dnszone
options:
ipaadmin_principal:
description: The admin principal
default: admin
ipaadmin_password:
description: The admin password
required: false
name:
description: The zone name string.
required: true
type: str
alises: ["zone_name"]
forwarders:
description: The list of global DNS forwarders.
required: false
options:
ip_address:
description: The forwarder nameserver IP address list (IPv4 and IPv6).
required: true
port:
description: The port to forward requests to.
required: false
forward_policy:
description:
Global forwarding policy. Set to "none" to disable any configured
global forwarders.
required: false
choices: ['only', 'first', 'none']
allow_sync_ptr:
description:
Allow synchronization of forward (A, AAAA) and reverse (PTR) records.
required: false
type: bool
state:
description: State to ensure
default: present
choices: ["present", "absent", "enabled", "disabled"]
name_server:
description: Authoritative nameserver domain name
required: false
type: str
admin_email:
description: Administrator e-mail address
required: false
type: str
update_policy:
description: BIND update policy
required: false
type: str
dynamic_update:
description: Allow dynamic updates
required: false
type: bool
alises: ["dynamicupdate"]
dnssec:
description: Allow inline DNSSEC signing of records in the zone
required: false
type: bool
allow_transfer:
description: List of IP addresses or networks which are allowed to transfer the zone
required: false
type: bool
allow_query:
description: List of IP addresses or networks which are allowed to issue queries
required: false
type: bool
serial:
description: SOA record serial number
required: false
type: int
refresh:
description: SOA record refresh time
required: false
type: int
retry:
description: SOA record retry time
required: false
type: int
expire:
description: SOA record expire time
required: false
type: int
minimum:
description: How long should negative responses be cached
required: false
type: int
ttl:
description: Time to live for records at zone apex
required: false
type: int
default_ttl:
description: Time to live for records without explicit TTL definition
required: false
type: int
nsec3param_rec:
description: NSEC3PARAM record for zone in format: hash_algorithm flags iterations salt.
required: false
type: str
skip_overlap_check:
description: Force DNS zone creation even if it will overlap with an existing zone
required: false
type: bool
skip_nameserver_check:
description: Force DNS zone creation even if nameserver is not resolvable
required: false
type: bool
""" # noqa: E501
EXAMPLES = """
---
# Ensure the zone is present (very minimal)
- ipadnszone:
name: test.example.com
# Ensure the zone is present (all available arguments)
- ipadnszone:
name: test.example.com
ipaadmin_password: SomeADMINpassword
allow_sync_ptr: true
dynamic_update: true
dnssec: true
allow_transfer:
- 1.1.1.1
- 2.2.2.2
allow_query:
- 1.1.1.1
- 2.2.2.2
forwarders:
- ip_address: 8.8.8.8
- ip_address: 8.8.4.4
port: 52
serial: 1234
refresh: 3600
retry: 900
expire: 1209600
minimum: 3600
ttl: 60
default_ttl: 90
name_server: ipaserver.test.local.
admin_email: admin.admin@example.com
nsec3param_rec: "1 7 100 0123456789abcdef"
skip_overlap_check: true
skip_nameserver_check: true
state: present
# Ensure zone is present and disabled
- ipadnszone:
name: test.example.com
state: disabled
# Ensure zone is present and enabled
- ipadnszone:
name: test.example.com
state: enabled
"""
RETURN = """
"""
from ipapython.dnsutil import DNSName # noqa: E402
from ansible.module_utils.ansible_freeipa_module import (
FreeIPABaseModule,
is_ipv4_addr,
is_ipv6_addr,
is_valid_port,
) # noqa: E402
class DNSZoneModule(FreeIPABaseModule):
ipa_param_mapping = {
# Direct Mapping
"idnsforwardpolicy": "forward_policy",
"idnssoaserial": "serial",
"idnssoarefresh": "refresh",
"idnssoaretry": "retry",
"idnssoaexpire": "expire",
"idnssoaminimum": "minimum",
"dnsttl": "ttl",
"dnsdefaultttl": "default_ttl",
"idnsallowsyncptr": "allow_sync_ptr",
"idnsallowdynupdate": "dynamic_update",
"idnssecinlinesigning": "dnssec",
"idnsupdatepolicy": "update_policy",
# Mapping by method
"idnsforwarders": "get_ipa_idnsforwarders",
"idnsallowtransfer": "get_ipa_idnsallowtransfer",
"idnsallowquery": "get_ipa_idnsallowquery",
"idnssoamname": "get_ipa_idnssoamname",
"idnssoarname": "get_ipa_idnssoarname",
"skip_nameserver_check": "get_ipa_skip_nameserver_check",
"skip_overlap_check": "get_ipa_skip_overlap_check",
"nsec3paramrecord": "get_ipa_nsec3paramrecord",
}
def validate_ips(self, ips, error_msg):
invalid_ips = [
ip for ip in ips if not is_ipv4_addr(ip) or is_ipv6_addr(ip)
]
if any(invalid_ips):
self.fail_json(msg=error_msg % invalid_ips)
def is_valid_nsec3param_rec(self, nsec3param_rec):
try:
part1, part2, part3, part4 = nsec3param_rec.split(" ")
except ValueError:
return False
if not all([part1.isdigit(), part2.isdigit(), part3.isdigit()]):
return False
if not 0 <= int(part1) <= 255:
return False
if not 0 <= int(part2) <= 255:
return False
if not 0 <= int(part3) <= 65535:
return False
try:
int(part4, 16)
except ValueError:
is_hex = False
else:
is_hex = True
even_digits = len(part4) % 2 == 0
is_dash = part4 == "-"
# If not hex with even digits or dash then
# part4 is invalid
if not ((is_hex and even_digits) or is_dash):
return False
return True
def get_ipa_nsec3paramrecord(self):
nsec3param_rec = self.ipa_params.nsec3param_rec
if nsec3param_rec is not None:
error_msg = (
"Invalid nsec3param_rec: %s. "
"Expected format: <0-255> <0-255> <0-65535> "
"even-length_hexadecimal_digits_or_hyphen"
) % nsec3param_rec
if not self.is_valid_nsec3param_rec(nsec3param_rec):
self.fail_json(msg=error_msg)
return nsec3param_rec
def get_ipa_idnsforwarders(self):
if self.ipa_params.forwarders is not None:
forwarders = []
for forwarder in self.ipa_params.forwarders:
ip_address = forwarder.get("ip_address")
if not (is_ipv4_addr(ip_address) or is_ipv6_addr(ip_address)):
self.fail_json(
msg="Invalid IP for DNS forwarder: %s" % ip_address
)
port = forwarder.get("port", None)
if port and not is_valid_port(port):
self.fail_json(
msg="Invalid port number for DNS forwarder: %s %s"
% (ip_address, port)
)
formatted_forwarder = ip_address
port = forwarder.get("port")
if port:
formatted_forwarder += " port %d" % port
forwarders.append(formatted_forwarder)
return forwarders
def get_ipa_idnsallowtransfer(self):
if self.ipa_params.allow_transfer is not None:
error_msg = "Invalid ip_address for DNS allow_transfer: %s"
self.validate_ips(self.ipa_params.allow_transfer, error_msg)
return (";".join(self.ipa_params.allow_transfer) or "none") + ";"
def get_ipa_idnsallowquery(self):
if self.ipa_params.allow_query is not None:
error_msg = "Invalid ip_address for DNS allow_query: %s"
self.validate_ips(self.ipa_params.allow_query, error_msg)
return (";".join(self.ipa_params.allow_query) or "any") + ";"
@staticmethod
def _replace_at_symbol_in_rname(rname):
"""
See RFC 1035 for more information.
Section 8. MAIL SUPPORT
https://tools.ietf.org/html/rfc1035#section-8
"""
if "@" not in rname:
return rname
name, domain = rname.split("@")
name = name.replace(".", r"\.")
return ".".join((name, domain))
def get_ipa_idnssoarname(self):
if self.ipa_params.admin_email is not None:
return DNSName(
self._replace_at_symbol_in_rname(self.ipa_params.admin_email)
)
def get_ipa_idnssoamname(self):
if self.ipa_params.name_server is not None:
return DNSName(self.ipa_params.name_server)
def get_ipa_skip_overlap_check(self):
if not self.zone and self.ipa_params.skip_overlap_check is not None:
return self.ipa_params.skip_overlap_check
def get_ipa_skip_nameserver_check(self):
if not self.zone and self.ipa_params.skip_nameserver_check is not None:
return self.ipa_params.skip_nameserver_check
def get_zone(self, zone_name):
get_zone_args = {"idnsname": zone_name, "all": True}
response = self.api_command("dnszone_find", args=get_zone_args)
if response["count"] == 1:
self.zone = response["result"][0]
self.is_zone_active = self.zone.get("idnszoneactive") == ["TRUE"]
return self.zone
# Zone doesn't exist yet
self.zone = None
self.is_zone_active = False
@property
def zone_name(self):
return self.ipa_params.name
def define_ipa_commands(self):
# Look for existing zone in IPA
self.get_zone(self.zone_name)
args = self.get_ipa_command_args()
just_added = False
if self.ipa_params.state in ["present", "enabled", "disabled"]:
if not self.zone:
# Since the zone doesn't exist we just create it
# with given args
self.add_ipa_command("dnszone_add", self.zone_name, args)
self.is_zone_active = True
just_added = True
else:
# Zone already exist so we need to verify if given args
# matches the current config. If not we updated it.
if self.require_ipa_attrs_change(args, self.zone):
self.add_ipa_command("dnszone_mod", self.zone_name, args)
if self.ipa_params.state == "enabled" and not self.is_zone_active:
self.add_ipa_command("dnszone_enable", self.zone_name)
if self.ipa_params.state == "disabled" and self.is_zone_active:
self.add_ipa_command("dnszone_disable", self.zone_name)
if self.ipa_params.state == "absent":
if self.zone:
self.add_ipa_command("dnszone_del", self.zone_name)
# Due to a bug in FreeIPA dnszone-add won't set
# SOA Serial. The good news is that dnszone-mod does the job.
# See: https://pagure.io/freeipa/issue/8227
# Because of that, if the zone was just added with a given serial
# we run mod just after to workaround the bug
if just_added and self.ipa_params.serial is not None:
args = {
"idnssoaserial": self.ipa_params.serial,
}
self.add_ipa_command("dnszone_mod", self.zone_name, args)
def get_argument_spec():
forwarder_spec = dict(
ip_address=dict(type=str, required=True),
port=dict(type=int, required=False, default=None),
)
return dict(
state=dict(
type="str",
default="present",
choices=["present", "absent", "enabled", "disabled"],
),
ipaadmin_principal=dict(type="str", default="admin"),
ipaadmin_password=dict(type="str", required=False, no_log=True),
name=dict(
type="str", default=None, required=True, aliases=["zone_name"]
),
forwarders=dict(
type="list",
default=None,
required=False,
options=dict(**forwarder_spec),
),
forward_policy=dict(
type="str",
required=False,
default=None,
choices=["only", "first", "none"],
),
name_server=dict(type="str", required=False, default=None),
admin_email=dict(type="str", required=False, default=None),
allow_sync_ptr=dict(type="bool", required=False, default=None),
update_policy=dict(type="str", required=False, default=None),
dynamic_update=dict(
type="bool",
required=False,
default=None,
aliases=["dynamicupdate"],
),
dnssec=dict(type="bool", required=False, default=None),
allow_transfer=dict(type="list", required=False, default=None),
allow_query=dict(type="list", required=False, default=None),
serial=dict(type="int", required=False, default=None),
refresh=dict(type="int", required=False, default=None),
retry=dict(type="int", required=False, default=None),
expire=dict(type="int", required=False, default=None),
minimum=dict(type="int", required=False, default=None),
ttl=dict(type="int", required=False, default=None),
default_ttl=dict(type="int", required=False, default=None),
nsec3param_rec=dict(type="str", required=False, default=None),
skip_nameserver_check=dict(type="bool", required=False, default=None),
skip_overlap_check=dict(type="bool", required=False, default=None),
)
def main():
DNSZoneModule(argument_spec=get_argument_spec()).ipa_run()
if __name__ == "__main__":
main()

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

@@ -76,6 +76,7 @@ import inspect
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
setup_logging,
paths, x509, NUM_VERSION, serialization, certdb, api,
delete_persistent_client_session_data, write_tmp_file,
ipa_generate_password, CalledProcessError, errors, disable_ra, DN,
@@ -95,9 +96,10 @@ def main():
)
module._ansible_debug = True
setup_logging()
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

@@ -67,6 +67,7 @@ import os
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
setup_logging,
SECURE_PATH, paths, sysrestore, options, NUM_VERSION, get_ca_cert,
get_ca_certs, errors
)
@@ -83,6 +84,8 @@ def main():
)
module._ansible_debug = True
setup_logging()
servers = module.params.get('servers')
realm = module.params.get('realm')
basedn = module.params.get('basedn')

View File

@@ -53,7 +53,7 @@ RETURN = '''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
paths, sysrestore
setup_logging, paths, sysrestore
)
@@ -65,6 +65,8 @@ def main():
)
module._ansible_debug = True
setup_logging()
backup = module.params.get('backup')
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)

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

@@ -70,7 +70,7 @@ RETURN = '''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
paths, sysrestore, configure_ipa_conf
setup_logging, paths, sysrestore, configure_ipa_conf
)
@@ -87,6 +87,8 @@ def main():
)
module._ansible_debug = True
setup_logging()
servers = module.params.get('servers')
domain = module.params.get('domain')
realm = module.params.get('realm')

View File

@@ -127,6 +127,7 @@ import tempfile
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
setup_logging,
SECURE_PATH, sysrestore, paths, options, configure_krb5_conf,
realm_to_suffix, kinit_keytab, GSSError, kinit_password, NUM_VERSION,
get_ca_cert, get_ca_certs, errors, run
@@ -155,6 +156,8 @@ def main():
)
module._ansible_debug = True
setup_logging()
servers = module.params.get('servers')
domain = module.params.get('domain')
realm = module.params.get('realm')

View File

@@ -54,7 +54,7 @@ RETURN = '''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
sysrestore, paths, tasks
setup_logging, sysrestore, paths, tasks
)
@@ -67,6 +67,8 @@ def main():
)
module._ansible_debug = True
setup_logging()
hostname = module.params.get('hostname')
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)

View File

@@ -60,7 +60,7 @@ RETURN = '''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
options, configure_automount
setup_logging, options, configure_automount
)
@@ -77,6 +77,8 @@ def main():
# os.environ['KRB5CCNAME'] = paths.IPA_DNS_CCACHE
module._ansible_debug = True
setup_logging()
options.servers = module.params.get('servers')
options.server = options.servers
options.sssd = module.params.get('sssd')

View File

@@ -60,7 +60,7 @@ RETURN = '''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
sysrestore, paths, options, configure_firefox
setup_logging, sysrestore, paths, options, configure_firefox
)
@@ -74,6 +74,8 @@ def main():
)
module._ansible_debug = True
setup_logging()
domain = module.params.get('domain')
options.firefox_dir = module.params.get('firefox_dir')

View File

@@ -81,7 +81,7 @@ RETURN = '''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
sysrestore, paths, configure_krb5_conf, logger
setup_logging, sysrestore, paths, configure_krb5_conf, logger
)
@@ -103,6 +103,8 @@ def main():
)
module._ansible_debug = True
setup_logging()
servers = module.params.get('servers')
domain = module.params.get('domain')
realm = module.params.get('realm')

View File

@@ -58,7 +58,7 @@ import inspect
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
options, sysrestore, paths, configure_nisdomain
setup_logging, options, sysrestore, paths, configure_nisdomain
)
@@ -72,6 +72,8 @@ def main():
)
module._ansible_debug = True
setup_logging()
domain = module.params.get('domain')
options.nisdomain = module.params.get('nisdomain')

View File

@@ -138,6 +138,7 @@ import inspect
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
setup_logging,
options, sysrestore, paths, ansible_module_get_parsed_ip_addresses,
api, errors, create_ipa_nssdb, ipautil, ScriptError, CLIENT_INSTALL_ERROR,
get_certs_from_ldap, DN, certstore, x509, logger, certdb,
@@ -179,6 +180,8 @@ def main():
)
module._ansible_debug = True
setup_logging()
cli_server = module.params.get('servers')
cli_realm = module.params.get('realm')
hostname = module.params.get('hostname')

View File

@@ -67,6 +67,7 @@ import inspect
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
setup_logging,
options, sysrestore, paths, sync_time, logger, ipadiscovery,
timeconf
)
@@ -89,6 +90,8 @@ def main():
)
# module._ansible_debug = True
setup_logging()
options.ntp_servers = module.params.get('ntp_servers')
options.ntp_pool = module.params.get('ntp_pool')
options.no_ntp = module.params.get('no_ntp')

View File

@@ -68,6 +68,7 @@ RETURN = '''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
setup_logging,
options, sysrestore, paths, configure_ssh_config, configure_sshd_config
)
@@ -85,6 +86,8 @@ def main():
)
module._ansible_debug = True
setup_logging()
options.servers = module.params.get('servers')
options.server = options.servers
options.no_ssh = module.params.get('no_ssh')

View File

@@ -101,7 +101,7 @@ RETURN = '''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
options, sysrestore, paths, configure_sssd_conf, logger
setup_logging, options, sysrestore, paths, configure_sssd_conf, logger
)
@@ -130,6 +130,8 @@ def main():
# options.set_logger(ansible_log)
module._ansible_debug = True
setup_logging()
cli_server = module.params.get('servers')
cli_domain = module.params.get('domain')
cli_realm = module.params.get('realm')

View File

@@ -199,6 +199,7 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
setup_logging,
paths, sysrestore, options, CheckedIPAddress, validate_domain_name,
logger, x509, normalize_hostname, installer, version, ScriptError,
CLIENT_INSTALL_ERROR, tasks, check_ldap_conf, timeconf, constants,
@@ -234,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)))
@@ -242,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()
@@ -290,6 +289,8 @@ def main():
)
# module._ansible_debug = True
setup_logging()
options.domain_name = module.params.get('domain')
options.servers = module.params.get('servers')
options.realm_name = module.params.get('realm')

View File

@@ -103,6 +103,7 @@ import tempfile
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
setup_logging,
SECURE_PATH, paths, kinit_keytab, run, GSSError, configure_krb5_conf
)
@@ -121,6 +122,8 @@ def main():
)
module._ansible_debug = True
setup_logging()
servers = module.params.get('servers')
domain = module.params.get('domain')
realm = module.params.get('realm')

View File

@@ -259,9 +259,6 @@ if NUM_VERSION >= 40400:
sssd_enable_ifp = None
logger = logging.getLogger("ipa-client-install")
standard_logging_setup(
paths.IPACLIENT_INSTALL_LOG, verbose=False, debug=False,
filemode='a', console_format='%(message)s')
root_logger = logger
else:
@@ -270,6 +267,12 @@ else:
raise Exception("freeipa version '%s' is too old" % VERSION)
def setup_logging():
standard_logging_setup(
paths.IPACLIENT_INSTALL_LOG, verbose=False, debug=False,
filemode='a', console_format='%(message)s')
def ansible_module_get_parsed_ip_addresses(ansible_module,
param='ip_addresses'):
ip_addresses = ansible_module.params.get(param)

View File

@@ -191,7 +191,7 @@
# 5 - Principal name or realm not found in keytab
failed_when: result_ipa_rmkeytab.rc != 0 and
result_ipa_rmkeytab.rc != 3 and result_ipa_rmkeytab.rc != 5
when: ipaclient_use_otp | bool or ipaclient_force_join | bool
when: (ipaclient_use_otp | bool or ipaclient_force_join | bool) and not ipaclient_on_master | bool
- name: Install - Backup and set hostname
ipaclient_set_hostname:

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

@@ -67,7 +67,7 @@ import six
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_replica import (
AnsibleModuleLog, installer, paths,
AnsibleModuleLog, setup_logging, installer, paths,
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
gen_remote_api, api
)
@@ -91,6 +91,7 @@ def main():
)
ansible_module._ansible_debug = True
setup_logging()
ansible_log = AnsibleModuleLog(ansible_module)
# get parameters #

View File

@@ -137,7 +137,7 @@ import os
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_replica import (
AnsibleModuleLog, installer, DN, paths,
AnsibleModuleLog, setup_logging, installer, DN, paths,
ansible_module_get_parsed_ip_addresses, sysrestore,
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
gen_ReplicaConfig, gen_remote_api, create_ipa_conf
@@ -186,6 +186,7 @@ def main():
)
ansible_module._ansible_debug = True
setup_logging()
ansible_log = AnsibleModuleLog(ansible_module)
# get parameters #

View File

@@ -99,7 +99,7 @@ import inspect
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_replica import (
AnsibleModuleLog, installer, DN, paths,
AnsibleModuleLog, setup_logging, installer, DN, paths,
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
gen_ReplicaConfig, gen_remote_api, redirect_stdout, custodiainstance
)
@@ -131,6 +131,7 @@ def main():
)
ansible_module._ansible_debug = True
setup_logging()
ansible_log = AnsibleModuleLog(ansible_module)
# get parameters #

View File

@@ -100,7 +100,7 @@ import os
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_replica import (
AnsibleModuleLog, installer, DN, paths,
AnsibleModuleLog, setup_logging, installer, DN, paths,
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
gen_ReplicaConfig, gen_remote_api, api, redirect_stdout,
replica_ds_init_info, dsinstance, upgradeinstance, installutils
@@ -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),
@@ -133,6 +133,7 @@ def main():
)
ansible_module._ansible_debug = True
setup_logging()
ansible_log = AnsibleModuleLog(ansible_module)
# get parameters #

View File

@@ -97,7 +97,7 @@ import os
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_replica import (
AnsibleModuleLog, installer, DN, paths,
AnsibleModuleLog, setup_logging, installer, DN, paths,
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
gen_ReplicaConfig, gen_remote_api, api, redirect_stdout,
replica_ds_init_info
@@ -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),
@@ -129,6 +129,7 @@ def main():
)
ansible_module._ansible_debug = True
setup_logging()
ansible_log = AnsibleModuleLog(ansible_module)
# get parameters #

View File

@@ -77,7 +77,7 @@ import os
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_replica import (
AnsibleModuleLog, installer, DN, paths,
AnsibleModuleLog, setup_logging, installer, DN, paths,
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
gen_ReplicaConfig, gen_remote_api, api, redirect_stdout, service,
find_providing_servers, services
@@ -103,6 +103,7 @@ def main():
)
ansible_module._ansible_debug = True
setup_logging()
ansible_log = AnsibleModuleLog(ansible_module)
# get parameters #

View File

@@ -137,7 +137,7 @@ import os
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_replica import (
AnsibleModuleLog, installer, DN, paths,
AnsibleModuleLog, setup_logging, installer, DN, paths,
ansible_module_get_parsed_ip_addresses,
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
gen_ReplicaConfig, gen_remote_api, api, redirect_stdout, ipaldap,
@@ -186,6 +186,7 @@ def main():
)
ansible_module._ansible_debug = True
setup_logging()
ansible_log = AnsibleModuleLog(ansible_module)
# get parameters #

View File

@@ -86,7 +86,7 @@ import os
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_replica import (
AnsibleModuleLog, installer, DN, paths, sysrestore,
AnsibleModuleLog, setup_logging, installer, DN, paths, sysrestore,
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
gen_ReplicaConfig, gen_remote_api, api, krbinstance, redirect_stdout
)
@@ -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),
),
@@ -114,6 +114,7 @@ def main():
)
ansible_module._ansible_debug = True
setup_logging()
ansible_log = AnsibleModuleLog(ansible_module)
# get parameters #

View File

@@ -53,7 +53,7 @@ password:
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_replica import (
ipa_generate_password
setup_logging, ipa_generate_password
)
@@ -67,6 +67,7 @@ def main():
)
module._ansible_debug = True
setup_logging()
master_password = module.params.get('master_password')

File diff suppressed because it is too large Load Diff

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