Compare commits

..

1 Commits

Author SHA1 Message Date
Rafael Guterres Jeffman
22700620c6 ipaconfig: Validate emaildomain
When setting the default email domain, there was no validation on the
provide value. Using ipapython.validate.Email applies the same
validation method as implemented in IPA.

Signed-off-by: Rafael Guterres Jeffman <rjeffman@redhat.com>
2025-03-05 16:49:11 -03:00
129 changed files with 1260 additions and 4496 deletions

View File

@@ -23,7 +23,7 @@ kinds:
- tasks: '**/tasks_*.yml' - tasks: '**/tasks_*.yml'
- tasks: '**/env_*.yml' - tasks: '**/env_*.yml'
# parseable: true parseable: true
quiet: false quiet: false

View File

@@ -5,7 +5,7 @@ on:
- pull_request - pull_request
jobs: jobs:
check_docs_oldest_supported: check_docs_oldest_supported:
name: Check Ansible Documentation with ansible-core 2.16. name: Check Ansible Documentation with ansible-core 2.13.
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.1.1
@@ -14,15 +14,15 @@ jobs:
- uses: actions/setup-python@v5.1.0 - uses: actions/setup-python@v5.1.0
with: with:
python-version: '3.x' python-version: '3.x'
- name: Install Ansible 2.16 - name: Install Ansible 2.13
run: | run: |
python -m pip install "ansible-core >=2.16,<2.17" python -m pip install "ansible-core >=2.13,<2.14"
- name: Run ansible-doc-test - name: Run ansible-doc-test
run: | run: |
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
check_docs_previous: check_docs_previous:
name: Check Ansible Documentation with ansible-core 2.18. name: Check Ansible Documentation with ansible-core 2.14.
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.1.1
@@ -31,15 +31,15 @@ jobs:
- uses: actions/setup-python@v5.1.0 - uses: actions/setup-python@v5.1.0
with: with:
python-version: '3.x' python-version: '3.x'
- name: Install Ansible 2.18 - name: Install Ansible 2.14
run: | run: |
python -m pip install "ansible-core >=2.18,<2.19" python -m pip install "ansible-core >=2.14,<2.15"
- name: Run ansible-doc-test - name: Run ansible-doc-test
run: | run: |
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
check_docs_current: check_docs_current:
name: Check Ansible Documentation with ansible-core 2.19. name: Check Ansible Documentation with ansible-core 2.15.
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.1.1
@@ -48,9 +48,9 @@ jobs:
- uses: actions/setup-python@v5.1.0 - uses: actions/setup-python@v5.1.0
with: with:
python-version: '3.x' python-version: '3.x'
- name: Install Ansible 2.20 - name: Install Ansible 2.15
run: | run: |
python -m pip install "ansible-core <2.20" python -m pip install "ansible-core >=2.15,<2.16"
- name: Run ansible-doc-test - name: Run ansible-doc-test
run: | run: |
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins

View File

@@ -13,12 +13,12 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-python@v5.1.0 - uses: actions/setup-python@v5.1.0
with: with:
python-version: "3.13" python-version: "3.x"
- name: Run ansible-lint - name: Run ansible-lint
run: | run: |
pip install "ansible-core>=2.16,<2.17" 'ansible-lint==6.22' pip install "ansible-core>=2.16,<2.17" 'ansible-lint==6.22'
utils/build-collection.sh -ki rpm utils/build-galaxy-release.sh -ki
cd .collection-build cd .galaxy-build
ansible-lint --profile production --exclude tests/integration/ --exclude tests/unit/ --parseable --nocolor ansible-lint --profile production --exclude tests/integration/ --exclude tests/unit/ --parseable --nocolor
yamllint: yamllint:

View File

@@ -1,7 +1,7 @@
--- ---
repos: repos:
- repo: https://github.com/ansible/ansible-lint.git - repo: https://github.com/ansible/ansible-lint.git
rev: v26.4.0 rev: v24.5.0
hooks: hooks:
- id: ansible-lint - id: ansible-lint
always_run: false always_run: false
@@ -18,18 +18,19 @@ repos:
--profile production --profile production
--exclude tests/integration/ --exclude tests/integration/
--exclude tests/unit/ --exclude tests/unit/
--parseable
--nocolor --nocolor
- repo: https://github.com/adrienverge/yamllint.git - repo: https://github.com/adrienverge/yamllint.git
rev: v1.38.0 rev: v1.35.1
hooks: hooks:
- id: yamllint - id: yamllint
files: \.(yaml|yml)$ files: \.(yaml|yml)$
- repo: https://github.com/pycqa/flake8 - repo: https://github.com/pycqa/flake8
rev: 7.3.0 rev: 7.0.0
hooks: hooks:
- id: flake8 - id: flake8
- repo: https://github.com/pycqa/pylint - repo: https://github.com/pycqa/pylint
rev: v4.0.6 rev: v3.2.2
hooks: hooks:
- id: pylint - id: pylint
args: args:

View File

@@ -145,7 +145,7 @@ Variable | Description | Required
`selinuxusermaporder` \| `ipaselinuxusermaporder`| Set ordered list in increasing priority of SELinux users | 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 `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 `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`, `pkinit`, `hardened`, `idp`, `passkey`, `disabled`, `""`). An additional check ensures that only types can be used that are supported by the IPA version. Use `""` to clear this variable. | no `user_auth_type` \| `ipauserauthtype` | set default types of supported user authentication (choices: `password`, `radius`, `otp`, `pkinit`, `hardened`, `idp`, `disabled`, `""`). An additional check ensures that only types can be used that are supported by the IPA version. Use `""` to clear this variable. | no
`domain_resolution_order` \| `ipadomainresolutionorder` | Set list of domains used for short name qualification | no `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 `ca_renewal_master_server` \| `ipacarenewalmasterserver`| Renewal master for IPA certificate authority. | no
`enable_sid` | New users and groups automatically get a SID assigned. Cannot be deactivated once activated. Requires IPA 4.9.8+. (bool) | no `enable_sid` | New users and groups automatically get a SID assigned. Cannot be deactivated once activated. Requires IPA 4.9.8+. (bool) | no

View File

@@ -281,99 +281,6 @@ Example playbook to ensure groups are absent:
``` ```
Example playbook to query a group and print the base fields:
```yaml
---
- name: Playbook to query groups
hosts: ipaserver
become: true
tasks:
- name: Query group ops
ipagroup:
ipaadmin_password: SomeADMINpassword
name: ops
state: query
register: result
- name: Print group info
debug:
var: result.group
```
Example playbook to query specific fields of a group:
```yaml
---
- name: Playbook to query groups
hosts: ipaserver
become: true
tasks:
- name: Query description and members of group ops
ipagroup:
ipaadmin_password: SomeADMINpassword
name: ops
query_param:
- description
- gid
- user
state: query
register: result
- name: Print group info
debug:
var: result.group
```
Example playbook to query all fields of a group:
```yaml
---
- name: Playbook to query groups
hosts: ipaserver
become: true
tasks:
- name: Query all fields of group ops
ipagroup:
ipaadmin_password: SomeADMINpassword
name: ops
query_param: ALL
state: query
register: result
- name: Print group info
debug:
var: result.group
```
Example playbook to query only the names of all groups:
```yaml
---
- name: Playbook to query groups
hosts: ipaserver
become: true
tasks:
- name: Query all group names
ipagroup:
ipaadmin_password: SomeADMINpassword
query_param: PKEY_ONLY
state: query
register: result
- name: Print group names
debug:
var: result.group.groups
```
Variables Variables
========= =========
@@ -399,10 +306,9 @@ Variable | Description | Required
`membermanager_group` | List of member manager groups assigned to this group. Only usable with IPA versions 4.8.4 and up. | no `membermanager_group` | List of member manager groups assigned to this group. Only usable with IPA versions 4.8.4 and up. | no
`externalmember` \| `ipaexternalmember` \| `external_member`| List of members of a trusted domain in DOM\\name or name@domain form. Requires "server" context. | no `externalmember` \| `ipaexternalmember` \| `external_member`| List of members of a trusted domain in DOM\\name or name@domain form. Requires "server" context. | no
`idoverrideuser` | List of user ID overrides to manage. Only usable with IPA versions 4.8.7 and up. Requires "server" context. | no `idoverrideuser` | List of user ID overrides to manage. Only usable with IPA versions 4.8.7 and up. Requires "server" context. | no
`rename` \| `new_name` | Rename the group object to the new name string. Only usable with `state: renamed`. | no `rename` \| `new_name` | Rename the user object to the new name string. Only usable with `state: renamed`. | no
`action` | Work on group or member level. It can be one of `member` or `group` and defaults to `group`. | no `action` | Work on group or member level. It can be on of `member` or `group` and defaults to `group`. | no
`query_param` | The fields to query with `state: query`. Can be `ALL`, `BASE`, `PKEY_ONLY` or a list of specific field names. Only usable with `state: query`. | no `state` | The state to ensure. It can be one of `present`, `absent` or `renamed`, default: `present`. | yes
`state` | The state to ensure. It can be one of `present`, `absent`, `renamed` or `query`, default: `present`. | yes
Authors Authors

View File

@@ -354,7 +354,7 @@ Variable | Description | Required
`mac_address` \| `macaddress` | List of hardware MAC addresses. | no `mac_address` \| `macaddress` | List of hardware MAC addresses. | no
`sshpubkey` \| `ipasshpubkey` | List of SSH public keys | no `sshpubkey` \| `ipasshpubkey` | List of SSH public keys | no
`userclass` \| `class` | Host category (semantics placed on this attribute are for local interpretation) | no `userclass` \| `class` | Host category (semantics placed on this attribute are for local interpretation) | no
`auth_ind` \| `krbprincipalauthind` | Defines an allow list for Authentication Indicators. Use 'otp' to allow OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA authentications. Use empty string to reset auth_ind to the initial value. Other values may be used for custom configurations. An additional check ensures that only types can be used that are supported by the IPA version. Choices: ["radius", "otp", "pkinit", "hardened", "idp", "passkey", ""] | no `auth_ind` \| `krbprincipalauthind` | Defines an allow list for Authentication Indicators. Use 'otp' to allow OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA authentications. Use empty string to reset auth_ind to the initial value. Other values may be used for custom configurations. An additional check ensures that only types can be used that are supported by the IPA version. Choices: ["radius", "otp", "pkinit", "hardened", "idp", ""] | no
`requires_pre_auth` \| `ipakrbrequirespreauth` | Pre-authentication is required for the service (bool) | no `requires_pre_auth` \| `ipakrbrequirespreauth` | Pre-authentication is required for the service (bool) | no
`ok_as_delegate` \| `ipakrbokasdelegate` | Client credentials may be delegated to the service (bool) | no `ok_as_delegate` \| `ipakrbokasdelegate` | Client credentials may be delegated to the service (bool) | no
`ok_to_auth_as_delegate` \| `ipakrboktoauthasdelegate` | The service is allowed to authenticate on behalf of a client (bool) | no `ok_to_auth_as_delegate` \| `ipakrboktoauthasdelegate` | The service is allowed to authenticate on behalf of a client (bool) | no

View File

@@ -68,6 +68,23 @@ Example playbook to ensure a local domain idrange is present:
name: local_domain_id_range name: local_domain_id_range
base_id: 150000 base_id: 150000
range_size: 200000 range_size: 200000
```
Example playbook to ensure a local domain idrange is present, with RID and secondary RID base values:
```yaml
---
- name: Playbook to manage IPA idrange.
hosts: ipaserver
become: no
tasks:
- name: Ensure local idrange is present
ipaidrange:
ipaadmin_password: SomeADMINpassword
name: local_domain_id_range
base_id: 150000000
range_size: 200000
rid_base: 1000000 rid_base: 1000000
secondary_rid_base: 200000000 secondary_rid_base: 200000000
``` ```
@@ -155,8 +172,8 @@ Variable | Description | Required
`name` \| `cn` | The list of idrange name strings. | yes `name` \| `cn` | The list of idrange name strings. | yes
`base_id` \| `ipabaseid` | First Posix ID of the range. (int) | yes, if `state: present` `base_id` \| `ipabaseid` | First Posix ID of the range. (int) | yes, if `state: present`
`range_size` \| `ipaidrangesize` | Number of IDs in the range. (int) | yes, if `state: present` `range_size` \| `ipaidrangesize` | Number of IDs in the range. (int) | yes, if `state: present`
`rid_base` \| `ipabaserid` | First RID of the corresponding RID range. (int) | yes, if `idrange_type: ipa-local` and `state: present` | `rid_base` \| `ipabaserid` | First RID of the corresponding RID range. (int) | no
`secondary_rid_base` \| `ipasecondarybaserid` | First RID of the secondary RID range. (int) | yes, if `idrange_type: ipa-local` and `state: present` | `secondary_rid_base` \| `ipasecondarybaserid` | First RID of the secondary RID range. (int) | no
`dom_sid` \| `ipanttrusteddomainsid` | Domain SID of the trusted domain. | no `dom_sid` \| `ipanttrusteddomainsid` | Domain SID of the trusted domain. | no
`idrange_type` \| `iparangetype` | ID range type, one of `ipa-ad-trust`, `ipa-ad-trust-posix`, `ipa-local`. Only valid if idrange does not exist. | no `idrange_type` \| `iparangetype` | ID range type, one of `ipa-ad-trust`, `ipa-ad-trust-posix`, `ipa-local`. Only valid if idrange does not exist. | no
`dom_name` \| `ipanttrusteddomainname` | Name of the trusted domain. Can only be used when `ipaapi_context: server`. | no `dom_name` \| `ipanttrusteddomainname` | Name of the trusted domain. Can only be used when `ipaapi_context: server`. | no

View File

@@ -1,88 +0,0 @@
Passkeyconfig module
============
Description
-----------
The passkeyconfig module allows to manage FreeIPA passkey configuration settings.
Features
--------
* Passkeyconfig management
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipapasskeyconfig module.
Requirements
------------
**Controller**
* Ansible version: 2.15+
**Node**
* Supported FreeIPA version (see above)
Usage
=====
Example inventory file
```ini
[ipaserver]
ipaserver.test.local
```
By default, user verification for passkey authentication is turned on (`true`). Example playbook to ensure that the requirement for user verification for passkey authentication is turned off:
```yaml
---
- name: Playbook to manage IPA passkeyconfig.
hosts: ipaserver
become: false
tasks:
- name: Ensure require_user_verification is false
ipapasskeyconfig:
ipaadmin_password: SomeADMINpassword
require_user_verification: false
```
Example playbook to get current passkeyconfig:
```yaml
---
- name: Playbook to get IPA passkeyconfig.
hosts: ipaserver
become: false
tasks:
- name: Retrieve current passkey configuration
ipapasskeyconfig:
ipaadmin_password: SomeADMINpassword
```
Variables
---------
Variable | Description | Required
-------- | ----------- | --------
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
`ipaapi_ldap_cache` | Use LDAP cache for IPA connection. The bool setting defaults to true. (bool) | no
`require_user_verification` \| `iparequireuserverification` | Require user verification for passkey authentication. (bool) | no
Authors
=======
Rafael Guterres Jeffman

View File

@@ -230,8 +230,6 @@ Example playbook to ensure that different members are not associated with a role
- User Administrators - User Administrators
service: service:
- service01 - service01
sysaccount:
- my-app
action: member action: member
state: absent state: absent
``` ```
@@ -255,8 +253,7 @@ Variable | Description | Required
`host` | List of hosts to be assigned or not assigned to the role. | no `host` | List of hosts to be assigned or not assigned to the role. | no
`hostgroup` | List of hostgroups to be assigned or not assigned to the role. | no `hostgroup` | List of hostgroups to be assigned or not assigned to the role. | no
`service` | List of services to be assigned or not assigned to the role. | no `service` | List of services to be assigned or not assigned to the role. | no
`sysaccount` | List of sysaccounts to be assigned or not assigned to the role. | no `action` | Work on role or member level. It can be on of `member` or `role` and defaults to `role`. | no
`action` | Work on role or member level. It can be one of `member` or `role` and defaults to `role`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no `state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no
@@ -264,4 +261,3 @@ Authors
======= =======
Rafael Jeffman Rafael Jeffman
Thomas Woerner

View File

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

View File

@@ -1,196 +0,0 @@
Sysaccount module
============
Description
-----------
The sysaccount module allows to ensure presence and absence of system accounts.
Features
--------
* Sysaccount management
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.4.0 and up are supported by the ipasysaccount module.
Requirements
------------
**Controller**
* Ansible version: 2.15+
**Node**
* Supported FreeIPA version (see above)
Usage
=====
Example inventory file
```ini
[ipaserver]
ipaserver.test.local
```
Example playbook to make sure sysaccount "my-app" is present with random password:
```yaml
---
- name: Playbook to manage IPA sysaccount.
hosts: ipaserver
become: false
tasks:
- name: Ensure sysaccount "my-app" is present with random password
ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
random: true
register: result
- name: Print generated random password
debug:
var: result.sysaccount.randompassword
```
Example playbook to make sure sysaccount "my-app" is present with given password:
```yaml
---
- name: Playbook to manage IPA sysaccount.
hosts: ipaserver
become: false
tasks:
- name: Ensure sysaccount "my-app" is present with given password
ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
password: SomeAPPpassword
```
Example playbook to make sure sysaccount "my-app" is absent:
```yaml
---
- name: Playbook to manage IPA sysaccount.
hosts: ipaserver
become: false
tasks:
- name: Ensure sysaccount "my-app" is absent
ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
state: absent
```
Example playbook to ensure existing sysaccount my-app is privileged
```yaml
---
- name: Playbook to manage IPA sysaccount.
hosts: ipaserver
become: false
tasks:
- name: Ensure existing sysaccount my-app is privileged
ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
privileged: true
```
Example playbook to ensure existing sysaccount my-app is not privileged
```yaml
---
- name: Playbook to manage IPA sysaccount.
hosts: ipaserver
become: false
tasks:
- name: Ensure existing sysaccount my-app is not privileged
ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
privileged: false
```
Example playbook to ensure existing sysaccount my-app is disabled
```yaml
---
- name: Playbook to manage IPA sysaccount.
hosts: ipaserver
become: false
tasks:
- name: Ensure existing sysaccount my-app is disabled
ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
state: disabled
```
Example playbook to ensure existing sysaccount my-app is enabled
```yaml
---
- name: Playbook to manage IPA sysaccount.
hosts: ipaserver
become: false
tasks:
- name: Ensure existing sysaccount my-app is enabled
ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
state: enabled
```
Variables
---------
Variable | Description | Required
-------- | ----------- | --------
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
`ipaapi_ldap_cache` | Use LDAP cache for IPA connection. The bool setting defaults to true. (bool) | no
`name` \| `login` | The list of sysaccount name strings - internally uid. (list of strings) | yes
`description` | A description for the sysaccount. (string) | no
`privileged` | Allow password updates without reset. This flag is not replicated. It is needed to set privileged on all servers, where it is needed. (bool) | no
`random` | Generate a random user password. (bool) | no
`password` \| `userpassword` | Set the password. (string) | no
`update_password` | Set password for a sysaccount in present state only on creation or always. It can be one of `always` or `on_create` and defaults to `always`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, 'enabled', 'disabled', default: `present`. | no
Return Values
=============
There are only return values if a random passwords has been generated.
Variable | Description | Returned When
-------- | ----------- | -------------
`sysaccount` | Sysaccount dict (dict) <br>Options: | Always
&nbsp; | `randompassword` - The generated random password | If random is yes and sysaccount did not exist or update_password is yes
Authors
=======
Thomas Woerner

View File

@@ -368,100 +368,6 @@ Example playbook to ensure users are absent:
state: absent state: absent
``` ```
Example playbook to query a user and print the base fields:
```yaml
---
- name: Playbook to query users
hosts: ipaserver
become: true
tasks:
- name: Query user pinky
ipauser:
ipaadmin_password: SomeADMINpassword
name: pinky
state: query
register: result
- name: Print user info
debug:
var: result.user
```
Example playbook to query specific fields of a user:
```yaml
---
- name: Playbook to query users
hosts: ipaserver
become: true
tasks:
- name: Query first and last name of user pinky
ipauser:
ipaadmin_password: SomeADMINpassword
name: pinky
query_param:
- first
- last
- email
state: query
register: result
- name: Print user info
debug:
var: result.user
```
Example playbook to query all fields of a user:
```yaml
---
- name: Playbook to query users
hosts: ipaserver
become: true
tasks:
- name: Query all fields of user pinky
ipauser:
ipaadmin_password: SomeADMINpassword
name: pinky
query_param: ALL
state: query
register: result
- name: Print user info
debug:
var: result.user
```
Example playbook to query only the names of all users:
```yaml
---
- name: Playbook to query users
hosts: ipaserver
become: true
tasks:
- name: Query all user names
ipauser:
ipaadmin_password: SomeADMINpassword
query_param: PKEY_ONLY
state: query
register: result
- name: Print user names
debug:
var: result.user.users
```
When using FreeIPA 4.8.0+, SMB logon script, profile, home directory and home drive can be set for users. When using FreeIPA 4.8.0+, SMB logon script, profile, home directory and home drive can be set for users.
In the example playbook to set SMB attributes note that `smb_profile_path` and `smb_home_dir` use paths in UNC format, which includes backslashes ('\\`). If the paths are quoted, the backslash needs to be escaped becoming "\\", so the path `\\server\dir` becomes `"\\\\server\\dir"`. If the paths are unquoted the slashes do not have to be escaped. In the example playbook to set SMB attributes note that `smb_profile_path` and `smb_home_dir` use paths in UNC format, which includes backslashes ('\\`). If the paths are quoted, the backslash needs to be escaped becoming "\\", so the path `\\server\dir` becomes `"\\\\server\\dir"`. If the paths are unquoted the slashes do not have to be escaped.
@@ -510,7 +416,7 @@ Variable | Description | Required
`update_password` | Set password for a user in present state only on creation or always. It can be one of `always` or `on_create` and defaults to `always`. | no `update_password` | Set password for a user in present state only on creation or always. It can be one of `always` or `on_create` and defaults to `always`. | no
`preserve` | Delete a user, keeping the entry available for future use. (bool) | no `preserve` | Delete a user, keeping the entry available for future use. (bool) | no
`action` | Work on user or member level. It can be on of `member` or `user` and defaults to `user`. | no `action` | Work on user or member level. It can be on of `member` or `user` and defaults to `user`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, `enabled`, `disabled`, `renamed`, `unlocked`, `undeleted` or `query`, default: `present`. Only `names` or `users` with only `name` set are allowed if state is not `present`. | yes `state` | The state to ensure. It can be one of `present`, `absent`, `enabled`, `disabled`, `renamed`, `unlocked` or `undeleted`, default: `present`. Only `names` or `users` with only `name` set are allowed if state is not `present`. | yes
@@ -546,7 +452,7 @@ Variable | Description | Required
`manager` | List of manager user names. | no `manager` | List of manager user names. | no
`carlicense` | List of car licenses. | no `carlicense` | List of car licenses. | no
`sshpubkey` \| `ipasshpubkey` | List of SSH public keys. | no `sshpubkey` \| `ipasshpubkey` | List of SSH public keys. | no
`userauthtype` \| `ipauserauthtype` | List of supported user authentication types. Choices: `password`, `radius`, `otp`, `pkinit`, `hardened`, `idp`, `passkey` and `""`. An additional check ensures that only types can be used that are supported by the IPA version. Use empty string to reset userauthtype to the initial value. | no `userauthtype` \| `ipauserauthtype` | List of supported user authentication types. Choices: `password`, `radius`, `otp`, `pkinit`, `hardened`, `idp` and `""`. An additional check ensures that only types can be used that are supported by the IPA version. Use empty string to reset userauthtype to the initial value. | no
`userclass` | User category. (semantics placed on this attribute are for local interpretation). | no `userclass` | User category. (semantics placed on this attribute are for local interpretation). | no
`radius` | RADIUS proxy configuration | no `radius` | RADIUS proxy configuration | no
`radiususer` | RADIUS proxy username | no `radiususer` | RADIUS proxy username | no

View File

@@ -38,7 +38,6 @@ Features
* Modules for idview management * Modules for idview management
* Modules for location management * Modules for location management
* Modules for netgroup management * Modules for netgroup management
* Modules for passkeyconfig management
* Modules for permission management * Modules for permission management
* Modules for privilege management * Modules for privilege management
* Modules for pwpolicy management * Modules for pwpolicy management
@@ -51,7 +50,6 @@ Features
* Modules for sudocmd management * Modules for sudocmd management
* Modules for sudocmdgroup management * Modules for sudocmdgroup management
* Modules for sudorule management * Modules for sudorule management
* Modules for sysaccount management
* Modules for topology management * Modules for topology management
* Modules for trust management * Modules for trust management
* Modules for user management * Modules for user management
@@ -455,7 +453,6 @@ Modules in plugin/modules
* [idview](README-idview.md) * [idview](README-idview.md)
* [ipalocation](README-location.md) * [ipalocation](README-location.md)
* [ipanetgroup](README-netgroup.md) * [ipanetgroup](README-netgroup.md)
* [ipapasskeyconfig](README-passkeyconfig.md)
* [ipapermission](README-permission.md) * [ipapermission](README-permission.md)
* [ipaprivilege](README-privilege.md) * [ipaprivilege](README-privilege.md)
* [ipapwpolicy](README-pwpolicy.md) * [ipapwpolicy](README-pwpolicy.md)
@@ -468,7 +465,6 @@ Modules in plugin/modules
* [ipasudocmd](README-sudocmd.md) * [ipasudocmd](README-sudocmd.md)
* [ipasudocmdgroup](README-sudocmdgroup.md) * [ipasudocmdgroup](README-sudocmdgroup.md)
* [ipasudorule](README-sudorule.md) * [ipasudorule](README-sudorule.md)
* [ipasysaccount](README-sysaccount.md)
* [ipatopologysegment](README-topology.md) * [ipatopologysegment](README-topology.md)
* [ipatopologysuffix](README-topology.md) * [ipatopologysuffix](README-topology.md)
* [ipatrust](README-trust.md) * [ipatrust](README-trust.md)

View File

@@ -3,10 +3,10 @@ trigger:
- master - master
pool: pool:
vmImage: 'ubuntu-24.04' vmImage: 'ubuntu-20.04'
variables: variables:
ansible_version: "-core >=2.18,<2.19" ansible_version: "-core >=2.16,<2.17"
ansible_latest: "-core" ansible_latest: "-core"
ansible_minimum: "-core <2.16" ansible_minimum: "-core <2.16"
distros: "fedora-latest,c9s,c10s,fedora-rawhide" distros: "fedora-latest,c9s,c10s,fedora-rawhide"
@@ -36,7 +36,7 @@ stages:
# Supported distros # Supported distros
- ${{ each distro in split(variables.distros, ',') }}: - ${{ each distro in split(variables.distros, ',') }}:
- stage: ${{ replace(distro, '-', '_') }}_ansible_2_18 - stage: ${{ replace(distro, '-', '_') }}_ansible_2_16
dependsOn: [] dependsOn: []
jobs: jobs:
- template: templates/group_tests.yml - template: templates/group_tests.yml
@@ -49,7 +49,7 @@ stages:
# Galaxy on Fedora # Galaxy on Fedora
- stage: galaxy_fedora_latest_ansible_2_18 - stage: galaxy_fedora_latest_ansible_2_16
dependsOn: [] dependsOn: []
jobs: jobs:
- template: templates/group_tests.yml - template: templates/group_tests.yml

View File

@@ -10,26 +10,15 @@ schedules:
trigger: none trigger: none
pool: pool:
vmImage: 'ubuntu-24.04' vmImage: 'ubuntu-20.04'
parameters:
# Not really a parameter, but variables cannot be arrays or dicts
# This maps the distro LATEST version to the avaiable ansible-core
# version of the latest released compose.
- name: "distro_ansible_map"
type: object
default:
- { distro: "c8s", ansible_version: "<2.17", version_name: "2.16" }
# c9s should use 2.14, but this version has an invalid certificate
# and so is unsuable against ansible-galaxy.
- { distro: "c9s", ansible_version: "<2.17", version_name: "2.16" }
- { distro: "c10s", ansible_version: "<2.17", version_name: "2.16" }
variables: variables:
distros: "fedora-latest,c10s,c9s,fedora-rawhide" # We need to have two sets, as c8s is not supported by all ansible versions
ansible_version: "-core >=2.18,<2.19" recent_distros: "fedora-latest,fedora-rawhide,c10s,c9s"
distros: "fedora-latest,fedora-rawhide,c10s,c9s,c8s"
ansible_latest: "-core" ansible_latest: "-core"
ansible_minimum: "-core <2.16" ansible_minimum: "-core <2.16"
ansible_version: "-core >=2.16,<2.17"
stages: stages:
@@ -49,7 +38,7 @@ stages:
# Latest ansible # Latest ansible
- ${{ each distro in split(variables.distros, ',') }}: - ${{ each distro in split(variables.recent_distros, ',') }}:
- stage: ${{ replace(distro, '-', '_') }}_ansible_latest - stage: ${{ replace(distro, '-', '_') }}_ansible_latest
dependsOn: [] dependsOn: []
jobs: jobs:
@@ -61,44 +50,30 @@ stages:
skip_git_test: true skip_git_test: true
test_galaxy: false test_galaxy: false
# Galaxy with Latest ansible # Selected ansible-core version
- ${{ each distro in split(variables.distros, ',') }}: - ${{ each distro in split(variables.distros, ',') }}:
- stage: galaxy_${{ replace(distro, '-', '_') }}_ansible_latest - stage: ${{ replace(distro, '-', '_') }}_ansible_2_16
dependsOn: [] dependsOn: []
jobs: jobs:
- template: templates/group_tests.yml - template: templates/group_tests.yml
parameters: parameters:
build_number: $(Build.BuildNumber) build_number: $(Build.BuildNumber)
distro: ${{ distro }} distro: ${{ distro }}
ansible_version: ${{ variables.ansible_latest }} ansible_version: ${{ variables.ansible_version }}
skip_git_test: true
test_galaxy: true
# Test with pinned ansible version for the distro
- ${{ each config in parameters.distro_ansible_map }}:
- stage: ${{ config.distro }}_distro_ansible_${{ replace(config.version_name, '.', '_') }}
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
distro: ${{ config.distro }}
ansible_version: -core${{ config.ansible_version }}
skip_git_test: true skip_git_test: true
test_galaxy: false test_galaxy: false
# Test Galaxy collection with pinned ansible version for the distro # Galaxy collection with selected ansible-core version
- ${{ each config in parameters.distro_ansible_map }}: - ${{ each distro in split(variables.distros, ',') }}:
- stage: galaxy_${{ config.distro }}_distro_ansible_${{ replace(config.version_name, '.', '_') }} - stage: galaxy_${{ replace(distro, '-', '_') }}_asible_2_16
dependsOn: [] dependsOn: []
jobs: jobs:
- template: templates/group_tests.yml - template: templates/group_tests.yml
parameters: parameters:
build_number: $(Build.BuildNumber) build_number: $(Build.BuildNumber)
distro: ${{ config.distro }} distro: ${{ distro }}
ansible_version: -core${{ config.ansible_version }} ansible_version: ${{ variables.ansible_version }}
skip_git_test: true skip_git_test: true
test_galaxy: true test_galaxy: true

View File

@@ -3,31 +3,18 @@ trigger:
- master - master
pool: pool:
vmImage: 'ubuntu-24.04' vmImage: 'ubuntu-20.04'
parameters:
# Not really a parameter, but variables cannot be arrays or dicts
# This maps the distro LATEST version to the avaiable ansible-core
# version of the latest released compose.
- name: "distro_ansible_map"
type: object
default:
- { distro: "c8s", ansible_version: "<2.17", version_name: "2.16" }
# c9s should use 2.14, but this version has an invalid certificate
# and so is unsuable against ansible-galaxy.
- { distro: "c9s", ansible_version: "<2.17", version_name: "2.16" }
- { distro: "c10s", ansible_version: "<2.17", version_name: "2.16" }
variables: variables:
distros: "fedora-latest,c10s,c9s,fedora-rawhide" distros: "fedora-latest,c10s,c9s,c8s,fedora-rawhide"
ansible_version: "-core >=2.18,<2.19" ansible_version: "-core >=2.15,<2.16"
stages: stages:
# Test with repository in all "current" distros # Test with repository in all distros
- ${{ each distro in split(variables.distros, ',') }}: - ${{ each distro in split(variables.distros, ',') }}:
- stage: ${{ replace(distro, '-', '_') }}_ansible_2_18 - stage: ${{ replace(distro, '-', '_') }}_ansible_2_16
dependsOn: [] dependsOn: []
jobs: jobs:
- template: templates/run_tests.yml - template: templates/run_tests.yml
@@ -40,7 +27,7 @@ stages:
# Galaxy on Fedora # Galaxy on Fedora
- stage: galaxy_fedora_latest_ansible_2_18 - stage: galaxy_fedora_latest_ansible_2_16
dependsOn: [] dependsOn: []
jobs: jobs:
- template: templates/run_tests.yml - template: templates/run_tests.yml
@@ -50,18 +37,3 @@ stages:
ansible_version: ${{ variables.ansible_version }} ansible_version: ${{ variables.ansible_version }}
skip_git_test: false skip_git_test: false
test_galaxy: true test_galaxy: true
# Test with pinned ansible version for the distro
- ${{ each config in parameters.distro_ansible_map }}:
- stage: ${{ config.distro }}_distro_ansible_${{ replace(config.version_name, '.', '_') }}
dependsOn: []
jobs:
- template: templates/run_tests.yml
parameters:
build_number: $(Build.BuildNumber)
distro: ${{ config.distro }}
ansible_version: -core${{ config.ansible_version }}
skip_git_test: false
test_galaxy: false

View File

@@ -54,7 +54,7 @@ jobs:
- script: | - script: |
git fetch --unshallow git fetch --unshallow
utils/build-collection.sh -i rpm utils/build-galaxy-release.sh -i
retryCountOnTaskFailure: 5 retryCountOnTaskFailure: 5
displayName: Build Galaxy release displayName: Build Galaxy release
condition: ${{ parameters.test_galaxy }} condition: ${{ parameters.test_galaxy }}

View File

@@ -120,6 +120,13 @@ then
fi fi
echo echo
if $deployed; then
log info "= Enabling services ="
container_exec "${name}" systemctl enable fixnet
container_exec "${name}" systemctl enable fixipaip
echo
fi
container_stop "${name}" container_stop "${name}"
$deployed || die "Deployment failed" $deployed || die "Deployment failed"

View File

@@ -31,8 +31,6 @@ COPY system-service/fixipaip.sh /root/
COPY system-service/fixnet.service /etc/systemd/system/ COPY system-service/fixnet.service /etc/systemd/system/
COPY system-service/fixipaip.service /etc/systemd/system/ COPY system-service/fixipaip.service /etc/systemd/system/
RUN chmod +x /root/fixnet.sh /root/fixipaip.sh RUN chmod +x /root/fixnet.sh /root/fixipaip.sh
RUN systemctl enable fixnet.service
RUN systemctl enable fixipaip.service
STOPSIGNAL RTMIN+3 STOPSIGNAL RTMIN+3

View File

@@ -12,7 +12,6 @@ dnf --assumeyes install \
bash \ bash \
systemd \ systemd \
procps-ng \ procps-ng \
hostname \
iproute; \ iproute; \
dnf clean all; \ dnf clean all; \
rm -rf /var/cache/dnf/; rm -rf /var/cache/dnf/;
@@ -35,8 +34,6 @@ COPY system-service/fixipaip.sh /root/
COPY system-service/fixnet.service /etc/systemd/system/ COPY system-service/fixnet.service /etc/systemd/system/
COPY system-service/fixipaip.service /etc/systemd/system/ COPY system-service/fixipaip.service /etc/systemd/system/
RUN chmod +x /root/fixnet.sh /root/fixipaip.sh RUN chmod +x /root/fixnet.sh /root/fixipaip.sh
RUN systemctl enable fixnet.service
RUN systemctl enable fixipaip.service
STOPSIGNAL RTMIN+3 STOPSIGNAL RTMIN+3

View File

@@ -9,7 +9,6 @@ dnf --assumeyes install \
bash \ bash \
systemd \ systemd \
procps-ng \ procps-ng \
hostname \
iproute; \ iproute; \
rm -rf /var/cache/dnf/; rm -rf /var/cache/dnf/;
@@ -31,8 +30,6 @@ COPY system-service/fixipaip.sh /root/
COPY system-service/fixnet.service /etc/systemd/system/ COPY system-service/fixnet.service /etc/systemd/system/
COPY system-service/fixipaip.service /etc/systemd/system/ COPY system-service/fixipaip.service /etc/systemd/system/
RUN chmod +x /root/fixnet.sh /root/fixipaip.sh RUN chmod +x /root/fixnet.sh /root/fixipaip.sh
RUN systemctl enable fixnet.service
RUN systemctl enable fixipaip.service
STOPSIGNAL RTMIN+3 STOPSIGNAL RTMIN+3

View File

@@ -11,7 +11,6 @@ dnf --assumeyes install \
bash \ bash \
systemd \ systemd \
procps-ng \ procps-ng \
hostname \
iproute; \ iproute; \
dnf clean all; \ dnf clean all; \
rm -rf /var/cache/dnf/; rm -rf /var/cache/dnf/;
@@ -34,8 +33,6 @@ COPY system-service/fixipaip.sh /root/
COPY system-service/fixnet.service /etc/systemd/system/ COPY system-service/fixnet.service /etc/systemd/system/
COPY system-service/fixipaip.service /etc/systemd/system/ COPY system-service/fixipaip.service /etc/systemd/system/
RUN chmod +x /root/fixnet.sh /root/fixipaip.sh RUN chmod +x /root/fixnet.sh /root/fixipaip.sh
RUN systemctl enable fixnet.service
RUN systemctl enable fixipaip.service
STOPSIGNAL RTMIN+3 STOPSIGNAL RTMIN+3

View File

@@ -11,7 +11,6 @@ dnf --assumeyes install \
bash \ bash \
systemd \ systemd \
procps-ng \ procps-ng \
hostname \
iproute; \ iproute; \
dnf clean all; \ dnf clean all; \
rm -rf /var/cache/dnf/; rm -rf /var/cache/dnf/;
@@ -34,8 +33,6 @@ COPY system-service/fixipaip.sh /root/
COPY system-service/fixnet.service /etc/systemd/system/ COPY system-service/fixnet.service /etc/systemd/system/
COPY system-service/fixipaip.service /etc/systemd/system/ COPY system-service/fixipaip.service /etc/systemd/system/
RUN chmod +x /root/fixnet.sh /root/fixipaip.sh RUN chmod +x /root/fixnet.sh /root/fixipaip.sh
RUN systemctl enable fixnet.service
RUN systemctl enable fixipaip.service
STOPSIGNAL RTMIN+3 STOPSIGNAL RTMIN+3

View File

@@ -4,20 +4,13 @@
SCRIPTDIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")" SCRIPTDIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")"
TOPDIR="$(readlink -f "${SCRIPTDIR}/../..")" TOPDIR="$(readlink -f "${SCRIPTDIR}/../..")"
# shellcheck disable=SC1091
. "${SCRIPTDIR}/shdefaults"
# shellcheck disable=SC1091
. "${TOPDIR}/utils/shfun" . "${TOPDIR}/utils/shfun"
container_create() { container_create() {
local name=${1} local name=${1}
local image=${2} local image=${2}
shift 2 shift 2
declare -a extra_opts declare -a extra_opts=()
readarray -t extra_opts < \
<(sed -e "s/-/--cap-drop=/g" -e "s/+/--cap-add=/g" \
<<< "$(printf '%s\n' "${CAP_DEFAULTS[@]}")")
for opt in "$@" for opt in "$@"
do do
[ -z "${opt}" ] && continue [ -z "${opt}" ] && continue
@@ -26,7 +19,6 @@ container_create() {
cpus=*) extra_opts+=("--${opt}") ;; cpus=*) extra_opts+=("--${opt}") ;;
memory=*) extra_opts+=("--${opt}") ;; memory=*) extra_opts+=("--${opt}") ;;
capabilities=*) extra_opts+=("--cap-add=${opt##*=}") ;; capabilities=*) extra_opts+=("--cap-add=${opt##*=}") ;;
volume=*) extra_opts+=("--volume=${opt##*=}") ;;
*) log error "container_create: Invalid option: ${opt}" ;; *) log error "container_create: Invalid option: ${opt}" ;;
esac esac
done done
@@ -55,19 +47,6 @@ container_start() {
log info "= Starting ${name} =" log info "= Starting ${name} ="
podman start "${name}" podman start "${name}"
# Add host entry to /etc/hosts
ip=$(podman inspect "${name}" --format "{{.NetworkSettings.IPAddress}}")
hostname=$(podman inspect "${name}" --format "{{.Config.Hostname}}")
if [ -n "${ip}" ] && [ -n "${hostname}" ]; then
cmd=$(cat <<EOF
sed -i -E "/\s+${hostname}(\s|$)/d" /etc/hosts
echo -e "$ip\t${hostname} ${hostname%%.*}" >> /etc/hosts
EOF
)
podman exec "${name}" bash -c "$cmd"
fi
# Ensure /etc/shadow is readable
podman exec "${name}" bash -c "chmod u+r /etc/shadow"
echo echo
} }
@@ -216,34 +195,3 @@ container_fetch() {
podman cp "${name}:${source}" "${destination}" podman cp "${name}:${source}" "${destination}"
echo echo
} }
container_tee() {
local name=${1}
local destination=${2}
tmpfile=$(mktemp /tmp/container-temp.XXXXXX)
log info "= Creating ${name}:${destination} from stdin ="
cat - > "${tmpfile}"
podman cp "${tmpfile}" "${name}:${destination}"
rm "${tmpfile}"
echo
}
container_save() {
local name=${1}
archive="${name}.tar"
log info "= Saving ${name} to ${archive} ="
# podman is not able to overwrite the archive
[ -f "${archive}" ] && rm "${archive}"
podman save -o "${archive}" "${name}"
echo
}
container_load() {
local name=${1}
image_name=$(podman load -q -i "${name}" | sed -e "s/^Loaded image: //")
image=$(podman image list -q "${image_name}")
echo "$image"
}

View File

@@ -1,11 +0,0 @@
#!/bin/bash -eu
# This file is meant to be source'd by other scripts
# Set default capabilities options for freeipa containers.
# Use +CAP to add the capability and -CAP to drop the capability.
CAP_DEFAULTS=(
"+DAC_READ_SEARCH" # Required for SSSD
"+SYS_PTRACE" # Required for debugging
"+SYS_ADMIN" # Required to make dbus-brokder for systemd 258 work
# Should be "+AUDIT_WRITE", "+SETUID", "+SETGID"
)

View File

@@ -1,7 +1,6 @@
[Unit] [Unit]
Description=Fix IPA server IP in IPA Server Description=Fix IPA server IP in IPA Server
After=ipa.service After=ipa.service
PartOf=ipa.service
[Service] [Service]
Type=oneshot Type=oneshot
@@ -10,4 +9,4 @@ StandardOutput=journal
StandardError=journal StandardError=journal
[Install] [Install]
WantedBy=ipa.service WantedBy=default.target

View File

@@ -50,9 +50,9 @@ if [ -z "${FORWARDER}" ] || [ "${FORWARDER}" == "127.0.0.1" ]; then
fi fi
echo "Fix IPA:" echo "Fix IPA:"
echo " HOSTNAME: '${HOSTNAME}'" echo " HOSTNAME: '${HOSTNAME}'"
echo " IP: '${IP}'" echo " IP: '${IP}'"
echo " PTR: '${PTR}'" echo " PTR: '${PTR}'"
echo " FORWARDER: '${FORWARDER}'" echo " FORWARDER: '${FORWARDER}'"
ZONES=$(ipa -e in_server=true dnszone-find --name-from-ip="${HOSTNAME}." \ ZONES=$(ipa -e in_server=true dnszone-find --name-from-ip="${HOSTNAME}." \

View File

@@ -1,5 +1,8 @@
[Unit] [Unit]
Description=Fix /etc/hosts and with local DNS also /etc/resolv.conf Description=Fix server IP in IPA Server
Wants=network.target
After=network.target
Before=ipa.service
[Service] [Service]
Type=oneshot Type=oneshot
@@ -8,4 +11,4 @@ StandardOutput=journal
StandardError=journal StandardError=journal
[Install] [Install]
WantedBy=container-ipa.target WantedBy=ipa.service

View File

@@ -39,35 +39,26 @@ if [ -z "${IP}" ] || ! valid_ipv4 "${IP}" ; then
exit 1 exit 1
fi fi
DOMAIN=${HOSTNAME#*.}
echo "Fix NET:" echo "Fix NET:"
echo " HOSTNAME: '${HOSTNAME}'" echo " HOSTNAME: '${HOSTNAME}'"
echo " DOMAIN: '${DOMAIN}'" echo " IP: '${IP}'"
echo " IP: '${IP}'"
echo echo
# /etc/hosts if grep -qE "^[^(#\s*)][0-9\.]+\s$HOSTNAME(\s|$)" /etc/hosts
then
sed -i -E "/\s+${HOSTNAME}(\s|$)/d" /etc/hosts sed -i.bak -e "s/.*${HOSTNAME}/${IP}\t${HOSTNAME}/" /etc/hosts
echo -e "$IP\t${HOSTNAME} ${HOSTNAME%%.*}" >> /etc/hosts else
echo -e "$IP\t${HOSTNAME} ${HOSTNAME%%.*}" >> /etc/hosts
echo "/etc/hosts:" fi
cat "/etc/hosts"
# /etc/resolv.conf
# If bind is not installed, exit
[ -f "/etc/named.conf" ] || exit 0
# If dyndb is not enabled for bind, exit
grep -q '^dyndb "ipa"' "/etc/named.conf" || exit 0
cp -a /etc/resolv.conf /etc/resolv.conf.fixnet cp -a /etc/resolv.conf /etc/resolv.conf.fixnet
cat > /etc/resolv.conf <<EOF cat > /etc/resolv.conf <<EOF
search ${DOMAIN} search ${HOSTNAME#*.}
nameserver 127.0.0.1 nameserver 127.0.0.1
EOF EOF
echo "/etc/hosts:"
cat "/etc/hosts"
echo echo
echo "/etc/resolv.conf:" echo "/etc/resolv.conf:"
cat "/etc/resolv.conf" cat "/etc/resolv.conf"

View File

@@ -1,10 +0,0 @@
---
- name: Passkeyconfig example
hosts: ipaserver
become: no
tasks:
- name: Set passkeyconfig require_user_verification to false
ipapasskeyconfig:
ipaadmin_password: SomeADMINpassword
require_user_verification: false

View File

@@ -1,14 +0,0 @@
---
- name: Passkeyconfig get current configuration example
hosts: ipaserver
become: true
tasks:
- name: Get current passkey configuration
ipapasskeyconfig:
ipaadmin_password: SomeADMINpassword
register: result
- name: Display current passkey configuration
ansible.builtin.debug:
var: result.passkeyconfig

View File

@@ -1,11 +0,0 @@
---
- name: Sysaccount example
hosts: ipaserver
become: false
tasks:
- name: Ensure sysaccount my-app is absent
ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
state: absent

View File

@@ -1,11 +0,0 @@
---
- name: Sysaccount example
hosts: ipaserver
become: false
tasks:
- name: Ensure sysaccount my-app is disabled
ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
state: disabled

View File

@@ -1,11 +0,0 @@
---
- name: Sysaccount example
hosts: ipaserver
become: false
tasks:
- name: Ensure sysaccount my-app is enabled
ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
state: enabled

View File

@@ -1,11 +0,0 @@
---
- name: Sysaccount example
hosts: ipaserver
become: false
tasks:
- name: Ensure sysaccount my-app is present with random password
ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
random: true

View File

@@ -1,11 +0,0 @@
---
- name: Sysaccount example
hosts: ipaserver
become: false
tasks:
- name: Ensure sysaccount my-app is privileged
ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
privileged: true

View File

@@ -1,11 +0,0 @@
---
- name: Sysaccount example
hosts: ipaserver
become: false
tasks:
- name: Ensure sysaccount my-app is not privileged
ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
privileged: false

View File

@@ -107,7 +107,7 @@ from ansible.plugins.inventory import BaseInventoryPlugin
from ansible.module_utils.six.moves.urllib.parse import quote from ansible.module_utils.six.moves.urllib.parse import quote
class InventoryModule(BaseInventoryPlugin): # pylint: disable=R0901 class InventoryModule(BaseInventoryPlugin):
NAME = 'freeipa' NAME = 'freeipa'

View File

@@ -33,7 +33,7 @@ __all__ = ["DEBUG_COMMAND_ALL", "DEBUG_COMMAND_LIST",
"paths", "tasks", "get_credentials_if_valid", "Encoding", "paths", "tasks", "get_credentials_if_valid", "Encoding",
"DNSName", "getargspec", "certificate_loader", "DNSName", "getargspec", "certificate_loader",
"write_certificate_list", "boolean", "template_str", "write_certificate_list", "boolean", "template_str",
"urlparse", "normalize_sshpubkey"] "urlparse", "normalize_sshpubkey", "Email"]
DEBUG_COMMAND_ALL = 0b1111 DEBUG_COMMAND_ALL = 0b1111
# Print the while command list: # Print the while command list:
@@ -116,6 +116,7 @@ try:
from ipalib.krb_utils import get_credentials_if_valid from ipalib.krb_utils import get_credentials_if_valid
from ipapython.dnsutil import DNSName from ipapython.dnsutil import DNSName
from ipapython import kerberos from ipapython import kerberos
from ipapython.ipavalidate import Email
try: try:
from ipalib.x509 import Encoding from ipalib.x509 import Encoding
@@ -666,108 +667,6 @@ def gen_intersection_list(user_list, res_list):
return list(set(res_list or []).intersection(set(user_list or []))) return list(set(res_list or []).intersection(set(user_list or [])))
def gen_member_add_del_lists(param_mapping, params, res_find,
action, state):
"""
Compute member add/del lists for all member params in param_mapping.
Scans param_mapping for entries with "member": True and computes
add and del lists based on action and state:
- action != "member", state == "present": sync mode using
gen_add_del_lists (both additions and removals)
- action == "member", state == "present": add-only mode using
gen_add_list
- action == "member", state == "absent": remove-only mode using
gen_intersection_list
For any other action/state combination an empty dict is returned.
Parameters
----------
param_mapping: dict
The module's PARAM_MAPPING dict. Entries with "member": True
are processed.
params: dict
Extracted parameter values keyed by ansible parameter name.
res_find: dict
The IPA find result for the entity. May be an empty dict for
newly created entities.
action: str
The module's action value ("member" or the entity action name).
state: str
The module's state value ("present" or "absent").
Returns
-------
dict
Mapping of ansible parameter names to (add_list, del_list)
tuples.
"""
if state == "present" and action != "member":
mode = "sync"
elif state == "present" and action == "member":
mode = "add"
elif state == "absent" and action == "member":
mode = "remove"
else:
return {}
result = {}
for ansible_name, entry in param_mapping.items():
if not entry.get("member"):
continue
value = params.get(ansible_name)
find_key = entry.get("api_name", ansible_name)
existing = res_find.get(find_key)
if mode == "sync":
result[ansible_name] = gen_add_del_lists(value, existing)
elif mode == "add":
result[ansible_name] = (
gen_add_list(value, existing), [])
else:
result[ansible_name] = (
[], gen_intersection_list(value, existing))
return result
def gen_member_args_from_mapping(param_mapping, params):
"""
Build a member args dict for compare_args_ipa() from param_mapping.
Scans param_mapping for entries with "member": True and builds a
dict mapping the IPA attribute name (api_name) to the parameter
value for all member params where the value is not None.
This replaces per-module gen_member_args() functions.
Parameters
----------
param_mapping: dict
The module's PARAM_MAPPING dict.
params: dict
Extracted parameter values keyed by ansible parameter name.
Returns
-------
dict
IPA attribute names as keys, parameter values as values.
Suitable for passing to compare_args_ipa().
"""
_args = {}
for ansible_name, entry in param_mapping.items():
if not entry.get("member"):
continue
value = params.get(ansible_name)
if value is not None:
find_key = entry.get("api_name", ansible_name)
_args[find_key] = value
return _args
def encode_certificate(cert): def encode_certificate(cert):
""" """
Encode a certificate using base64. Encode a certificate using base64.
@@ -1328,8 +1227,7 @@ class IPAAnsibleModule(AnsibleModule):
return module_params_get_with_type_cast( return module_params_get_with_type_cast(
self, name, datatype, allow_empty) self, name, datatype, allow_empty)
def params_fail_used_invalid(self, invalid_params, state, action=None, def params_fail_used_invalid(self, invalid_params, state, action=None):
params=None, param_mapping=None):
""" """
Fail module execution if one of the invalid parameters is not None. Fail module execution if one of the invalid parameters is not None.
@@ -1341,12 +1239,6 @@ class IPAAnsibleModule(AnsibleModule):
State being tested. State being tested.
action: action:
Action being tested (optional). Action being tested (optional).
params:
Extracted params dict to check (optional, defaults to
self.params).
param_mapping:
Parameter mapping dict (optional). When provided, params
marked with "module_param" are checked via self.params.
""" """
if action is None: if action is None:
@@ -1355,15 +1247,8 @@ class IPAAnsibleModule(AnsibleModule):
msg = "Argument '{0}' can not be used with action "\ msg = "Argument '{0}' can not be used with action "\
"'{2}' and state '{1}'" "'{2}' and state '{1}'"
_params = params if params is not None else self.params
for param in invalid_params: for param in invalid_params:
if param_mapping is not None \ if self.params.get(param) is not None:
and param in param_mapping \
and param_mapping[param].get("module_param"):
value = self.params.get(param)
else:
value = _params.get(param)
if value is not None:
self.fail_json(msg=msg.format(param, state, action)) self.fail_json(msg=msg.format(param, state, action))
def ipa_command(self, command, name, args): def ipa_command(self, command, name, args):
@@ -1721,301 +1606,6 @@ class IPAAnsibleModule(AnsibleModule):
return changed return changed
@staticmethod
def _parse_mapping_entry(ansible_name, entry):
"""
Return (ansible_name, ipa_name) from a dict-of-dicts mapping entry.
If entry has "api_name", that is the IPA attribute name.
Otherwise, the IPA name equals the ansible name.
"""
return ansible_name, entry.get("api_name", ansible_name)
@staticmethod
def gen_args_from_mapping(param_mapping, params):
"""
Generate IPA command args dict from a parameter mapping.
Parameters
----------
param_mapping: dict
The module's parameter mapping dict. Keys are ansible param
names, values are dicts with optional keys: "api_name",
"nonempty_list", "convert_to", "gen_args", "return_only".
params: dict
Parameter values keyed by ansible name.
Returns
-------
dict
IPA attribute names as keys, suitable for IPA API commands
and compare_args_ipa().
"""
_args = {}
for ansible_name, entry in param_mapping.items():
if entry.get("gen_args") is False \
or entry.get("return_only") \
or entry.get("module_param"):
continue
ipa_name = entry.get("api_name", ansible_name)
value = params.get(ansible_name)
if value is None:
continue
if entry.get("nonempty_list") and len(value) == 0:
continue
convert = entry.get("convert_to")
if convert == "text":
value = to_text(str(value))
_args[ipa_name] = value
return _args
@staticmethod
def extract_params(module, param_mapping):
"""Extract parameter values from module params using mapping."""
params = {}
for ansible_name, entry in param_mapping.items():
if entry.get("return_only") or entry.get("module_param"):
continue
type_cast = entry.get("type_cast")
if type_cast is not None:
params[ansible_name] = module.params_get_with_type_cast(
ansible_name, type_cast,
allow_empty=entry.get("allow_empty", False))
elif entry.get("lowercase"):
allow_empty = entry.get("allow_empty_list_item")
params[ansible_name] = module.params_get_lowercase(
ansible_name, allow_empty_list_item=bool(allow_empty))
elif entry.get("allow_empty_list_item"):
params[ansible_name] = module.params_get(
ansible_name, allow_empty_list_item=True)
else:
params[ansible_name] = module.params_get(ansible_name)
return params
@staticmethod
def extract_params_from_entry(entry_dict, param_mapping):
"""Extract parameter values from a dict using mapping."""
params = {}
for ansible_name, entry in param_mapping.items():
if entry.get("return_only") or entry.get("module_param"):
continue
value = entry_dict.get(ansible_name)
if value is not None and entry.get("lowercase"):
value = convert_param_value_to_lowercase(value)
params[ansible_name] = value
return params
@staticmethod
def build_query_param_settings(param_mapping, query_fields):
"""
Build query_param_settings from a parameter mapping.
Parameters
----------
param_mapping: dict
The module's parameter mapping dict.
query_fields: list of str
Ansible-name fields for the BASE query set.
Returns
-------
dict
Dict with "ALL", "BASE", "mapping", and "param_mapping"
keys, compatible with execute_query().
"""
all_fields = []
mapping = {}
for ansible_name, entry in param_mapping.items():
if entry.get("query") is False \
or entry.get("module_param"):
continue
all_fields.append(ansible_name)
ipa_name = entry.get("api_name", ansible_name)
if ansible_name != ipa_name:
mapping[ansible_name] = ipa_name
return {
"ALL": all_fields,
"BASE": query_fields.get("base", []),
"PRIMARY_KEY": query_fields.get("primary_key", []),
"PREFIX": query_fields.get("prefix"),
"mapping": mapping,
"param_mapping": param_mapping,
}
def _extract_query_fields(self, result, fields, mapping,
param_mapping=None):
"""
Extract query fields from an IPA result using name mapping.
Parameters
----------
result: dict
The IPA result dict (after convert_result if provided).
fields: list of str
The Ansible-friendly field names to extract.
mapping: dict
Mapping of ansible_name -> ipa_attribute_name.
param_mapping: dict or None
The full parameter mapping dict. When provided, entry
metadata such as "type" is used for value conversion.
Returns
-------
dict
Extracted fields with Ansible-friendly names as keys.
"""
output = {}
for field in fields:
ipa_field = mapping.get(field, field)
if ipa_field in result:
value = result[ipa_field]
if param_mapping is not None:
entry = param_mapping.get(field, {})
field_type = entry.get("type")
try:
if field_type == "bool":
if isinstance(
value,
(str, unicode) # pylint: disable=W0012,E0606
):
value = value.lower() in ("true", "1", "yes")
else:
value = bool(value)
elif field_type == "int":
value = int(value)
except (ValueError, TypeError) as e:
self.fail_json(
msg="Parameter '%s' could not be converted "
"to %s: %s" % (field, field_type, str(e))
)
output[field] = value
return output
def execute_query(self, names, query_param, find_command,
query_param_settings, convert_result=None):
"""
Execute query state.
Parameters
----------
names: list of str or None
The item names to query. If None or empty, all items are
queried using find_command(module, None).
prefix: str
The grouping key for name-only results (e.g., "users").
name_ipa_param: str
The IPA attribute name for the item name (e.g., "uid").
query_param: list of str or None
The fields to return. ["ALL"] for all fields, ["BASE"] for
base fields, a custom list for specific fields, or None to
return only item names.
find_command: callable
Module function: find_command(module, name) returning a dict
for a single item or a list of dicts for all items (when
name is None). Returns None if not found.
query_param_settings: dict
Module-defined dict with "ALL" (list of all Ansible field
names), "BASE" (list of essential field names), and
"mapping" (dict of ansible_name -> ipa_attribute_name for
names that differ).
convert_result: callable or None
Optional function to convert a raw IPA result dict to
Ansible-friendly values (e.g., unwrap single-element lists,
encode certificates). Applied to each result before field
extraction. Default: None.
Returns
-------
dict
The query results structured for exit_json.
"""
if query_param is not None and \
set(query_param).intersection(["PKEY_ONLY", "BASE", "ALL"]):
if len(query_param) > 1:
self.fail_json(
msg="query_params PKEY_ONLY, BASE or ALL can "
"only be used alone"
)
exit_args = {}
mapping = query_param_settings.get("mapping", {})
param_mapping = query_param_settings.get("param_mapping")
prefix = query_param_settings.get("PREFIX")
name_ipa_param = query_param_settings.get("PRIMARY_KEY")
# Resolve query_param to a concrete field list
if query_param == ["PKEY_ONLY"]:
resolved_fields = None
elif query_param == ["BASE"]:
# explicit BASE
resolved_fields = query_param_settings["BASE"]
elif query_param == ["ALL"]:
resolved_fields = query_param_settings["ALL"]
elif query_param is not None:
resolved_fields = query_param
else:
# Use BASE always if query_param is not set
resolved_fields = query_param_settings["BASE"]
# Validate requested fields
if resolved_fields is not None:
all_fields = query_param_settings["ALL"]
for field in resolved_fields:
if field not in all_fields:
self.fail_json(
msg="query_param '%s' is not supported" % field)
# If no names have been given, use [None] to get all items with the
# find command
_names = names
if _names is None or not isinstance(_names, list):
_names = [None]
single_item = len(_names) == 1
# For all names in _names convert results and add the requested
# fields to exit_args
for name in _names:
results = find_command(self, name)
if not isinstance(results, list):
results = [results]
for result in results:
if result is None:
continue
if convert_result is not None:
result = convert_result(result)
if name_ipa_param not in result:
self.fail_json(
msg="execute_query: primary key '%s' missing "
"from result for '%s'" % (name_ipa_param, name)
)
item_name = result[name_ipa_param]
if query_param == ["PKEY_ONLY"]:
exit_args.setdefault(prefix, []).append(item_name)
else:
fields = self._extract_query_fields(
result, resolved_fields, mapping,
param_mapping)
if name is not None:
if single_item:
exit_args = fields
else:
exit_args[name] = fields
else:
exit_args[item_name] = fields
return exit_args
def tm_warn(self, warning): def tm_warn(self, warning):
ts = time.time() ts = time.time()
# pylint: disable=super-with-arguments # pylint: disable=super-with-arguments

View File

@@ -161,7 +161,7 @@ options:
type: list type: list
elements: str elements: str
choices: ["password", "radius", "otp", "pkinit", "hardened", "idp", choices: ["password", "radius", "otp", "pkinit", "hardened", "idp",
"passkey", "disabled", ""] "disabled", ""]
aliases: ["ipauserauthtype"] aliases: ["ipauserauthtype"]
ca_renewal_master_server: ca_renewal_master_server:
description: Renewal master for IPA certificate authority. description: Renewal master for IPA certificate authority.
@@ -344,7 +344,7 @@ config:
from ansible.module_utils.ansible_freeipa_module import \ from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, ipalib_errors IPAAnsibleModule, compare_args_ipa, ipalib_errors, Email
def config_show(module): def config_show(module):
@@ -426,7 +426,7 @@ def main():
user_auth_type=dict(type="list", elements="str", required=False, user_auth_type=dict(type="list", elements="str", required=False,
choices=["password", "radius", "otp", choices=["password", "radius", "otp",
"pkinit", "hardened", "idp", "pkinit", "hardened", "idp",
"passkey", "disabled", ""], "disabled", ""],
aliases=["ipauserauthtype"]), aliases=["ipauserauthtype"]),
ca_renewal_master_server=dict(type="str", required=False), ca_renewal_master_server=dict(type="str", required=False),
domain_resolution_order=dict(type="list", elements="str", domain_resolution_order=dict(type="list", elements="str",
@@ -515,6 +515,13 @@ def main():
msg="Argument '%s' must be between %d and %d." msg="Argument '%s' must be between %d and %d."
% (arg, minimum, maximum)) % (arg, minimum, maximum))
# verify email domain
emaildomain = params.get("ipadefaultemaildomain", None)
if emaildomain:
if not Email("test@{0}".format(emaildomain)):
ansible_module.fail_json(
msg="Invalid 'emaildomain' value: %s" % emaildomain)
changed = False changed = False
exit_args = {} exit_args = {}

View File

@@ -1454,13 +1454,11 @@ def define_commands_for_present_state(module, zone_name, entry, res_find):
# Create reverse records for existing records # Create reverse records for existing records
for ipv in ['a', 'aaaa']: for ipv in ['a', 'aaaa']:
record = '%srecord' % ipv record = '%srecord' % ipv
if ( if record in args and ('%s_extra_create_reverse' % ipv) in args:
record in args
and args.pop('%s_extra_create_reverse' % ipv, False)
):
cmds = create_reverse_ip_record( cmds = create_reverse_ip_record(
module, zone_name, name, args[record]) module, zone_name, name, args[record])
_commands.extend(cmds) _commands.extend(cmds)
del args['%s_extra_create_reverse' % ipv]
for record, fields in _RECORD_PARTS.items(): for record, fields in _RECORD_PARTS.items():
part_fields = [f for f in fields if f in args] part_fields = [f for f in fields if f in args]
if part_fields: if part_fields:
@@ -1622,6 +1620,7 @@ def main():
commands.extend(cmds) commands.extend(cmds)
# Execute commands # Execute commands
changed = ansible_module.execute_ipa_commands( changed = ansible_module.execute_ipa_commands(
commands, exception_handler=exception_handler) commands, exception_handler=exception_handler)

View File

@@ -200,17 +200,6 @@ options:
required: false required: false
type: list type: list
elements: str elements: str
query_param:
description:
- The fields to query with state=query.
- Can be `ALL`, `BASE`, `PKEY_ONLY` or a list of specific field names.
required: false
type: list
elements: str
choices: ["ALL", "BASE", "PKEY_ONLY", "dn", "objectclass", "ipauniqueid",
"ipantsecurityidentifier", "name", "description", "gid", "user",
"group", "service", "externalmember", "idoverrideuser",
"membermanager_user", "membermanager_group"]
action: action:
description: Work on group or member level description: Work on group or member level
type: str type: str
@@ -225,8 +214,7 @@ options:
description: State to ensure description: State to ensure
type: str type: str
default: present default: present
choices: ["present", "absent", "renamed", choices: ["present", "absent", "renamed"]
"query"]
author: author:
- Thomas Woerner (@t-woerner) - Thomas Woerner (@t-woerner)
""" """
@@ -334,39 +322,6 @@ EXAMPLES = """
ipaadmin_password: SomeADMINpassword ipaadmin_password: SomeADMINpassword
name: sysops,appops,ops, nongroup name: sysops,appops,ops, nongroup
state: absent state: absent
# Query base fields of a group
- ipagroup:
ipaadmin_password: SomeADMINpassword
name: ops
state: query
register: result
# Query specific fields of a group
- ipagroup:
ipaadmin_password: SomeADMINpassword
name: ops
query_param:
- description
- gid
- user
state: query
register: result
# Query all fields of a group
- ipagroup:
ipaadmin_password: SomeADMINpassword
name: ops
query_param: ALL
state: query
register: result
# Query only the names of all groups
- ipagroup:
ipaadmin_password: SomeADMINpassword
query_param: PKEY_ONLY
state: query
register: result
""" """
RETURN = """ RETURN = """
@@ -375,8 +330,8 @@ RETURN = """
from ansible.module_utils._text import to_text from ansible.module_utils._text import to_text
from ansible.module_utils.ansible_freeipa_module import \ from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, \ IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, \
gen_add_list, gen_intersection_list, gen_member_add_del_lists, \ gen_add_list, gen_intersection_list, api_check_param, \
api_check_param, convert_to_sid, ipalib_errors convert_to_sid
from ansible.module_utils import six from ansible.module_utils import six
if six.PY3: if six.PY3:
unicode = str unicode = str
@@ -393,67 +348,59 @@ else:
"deepcopy" in baseldap.LDAPObject.__json__.__code__.co_names "deepcopy" in baseldap.LDAPObject.__json__.__code__.co_names
def group_show(module, name): def find_group(module, name):
_args = {"all": True} _args = {
"all": True,
"cn": name,
}
try: _result = module.ipa_command("group_find", name, _args)
_result = module.ipa_command("group_show", name, _args).get("result")
except ipalib_errors.NotFound:
return None
# The returned services are of type ipapython.kerberos.Principal, if len(_result["result"]) > 1:
# also services are not case sensitive. Therefore services are module.fail_json(
# converted to lowercase strings to be able to do the comparison. msg="There is more than one group '%s'" % (name))
if "member_service" in _result: elif len(_result["result"]) == 1:
_result["member_service"] = \ _res = _result["result"][0]
[to_text(svc).lower() for svc in _result["member_service"]] # The returned services are of type ipapython.kerberos.Principal,
# user_find is returning SIDs, but user_show is not. Therefore convert # also services are not case sensitive. Therefore services are
# external users to SIDs. # converted to lowercase strings to be able to do the comparison.
if "ipaexternalmember" in _result: if "member_service" in _res:
_result["ipaexternalmember"] = \ _res["member_service"] = \
convert_to_sid(_result["ipaexternalmember"]) [to_text(svc).lower() for svc in _res["member_service"]]
return _result return _res
return None
def query_convert_result(module, res): def gen_args(description, gid, nomembers):
_res = {} _args = {}
for key in res: if description is not None:
try: _args["description"] = description
if key.startswith("member_") or key.startswith("membermanager_"): if gid is not None:
_res[key] = [to_text(svc) for svc in res[key]] _args["gidnumber"] = gid
elif isinstance(res[key], (list, tuple)): if nomembers is not None:
if len(res[key]) == 1: _args["nomembers"] = nomembers
_res[key] = to_text(res[key][0])
else: return _args
_res[key] = [to_text(item) for item in res[key]]
elif key in ["gidnumber"]:
_res[key] = int(res[key])
else:
_res[key] = to_text(res[key])
except (TypeError, ValueError) as e:
module.fail_json(
msg="Failed to convert query result for '%s': %s"
% (key, str(e)))
return _res
def group_find(module, name): def gen_member_args(user, group, service, externalmember, idoverrideuser):
_args = {"all": True} _args = {}
if user is not None:
_args["member_user"] = user
if group is not None:
_args["member_group"] = group
if service is not None:
_args["member_service"] = service
if externalmember is not None:
_args["member_external"] = externalmember
if idoverrideuser is not None:
_args["member_idoverrideuser"] = idoverrideuser
try: return _args
if name:
_args["cn"] = name
_result = module.ipa_command_no_name(
"group_find", _args).get("result")
if _result and name:
_result = _result[0]
except ipalib_errors.NotFound:
return None
return _result
def check_parameters(module, state, action, group_params): def check_parameters(module, state, action):
invalid = ["description", "gid", "posix", "nonposix", "external", invalid = ["description", "gid", "posix", "nonposix", "external",
"nomembers"] "nomembers"]
if action == "group": if action == "group":
@@ -468,14 +415,7 @@ def check_parameters(module, state, action, group_params):
invalid.extend(["user", "group", "service", "externalmember"]) invalid.extend(["user", "group", "service", "externalmember"])
else: else:
invalid.append("rename") invalid.append("rename")
module.params_fail_used_invalid(invalid, state, action)
if state == "query":
module.fail_json(
msg="check_parameters can not be used with action query.")
invalid.append("query_param")
module.params_fail_used_invalid(invalid, state, action, group_params,
PARAM_MAPPING)
def is_external_group(res_find): def is_external_group(res_find):
@@ -505,75 +445,6 @@ def check_objectclass_args(module, res_find, posix, external):
"`non-posix`.") "`non-posix`.")
def convert_params(module, group_params):
"""Convert parameter values in group_params in-place."""
nonposix = group_params.get("nonposix")
external = group_params.get("external")
posix = group_params.get("posix")
if all((posix, nonposix)) or \
all((posix, external)) or \
all((nonposix, external)):
module.fail_json(
msg="parameters are mutually exclusive for group "
"`{0}`: posix|nonposix|external".format(
group_params.get("name")))
if external is False:
module.fail_json(msg="group can not be non-external")
if nonposix is not None:
group_params["posix"] = not nonposix
PARAM_MAPPING = {
# Read-only system fields
"dn": {"return_only": True},
"objectclass": {"return_only": True},
"ipauniqueid": {"return_only": True},
"ipantsecurityidentifier": {"return_only": True},
# Query-only: name is the primary key
"name": {"api_name": "cn", "gen_args": False},
# Writable params (used in gen_args)
"description": {},
"gid": {"api_name": "gidnumber", "type": "int"},
# Query-only: members handled via separate member commands
"user": {"api_name": "member_user", "gen_args": False,
"lowercase": True, "member": True},
"group": {"api_name": "member_group", "gen_args": False,
"lowercase": True, "member": True},
"service": {"api_name": "member_service", "gen_args": False,
"lowercase": True, "member": True},
"externalmember": {"api_name": "ipaexternalmember", "gen_args": False},
"idoverrideuser": {"api_name": "member_idoverrideuser",
"gen_args": False},
"membermanager_user": {"gen_args": False, "lowercase": True,
"member": True},
"membermanager_group": {"gen_args": False, "lowercase": True,
"member": True},
# Writable params not queryable by name
"rename": {"gen_args": False, "query": False},
"nonposix": {"gen_args": False, "query": False},
"external": {"gen_args": False, "query": False},
"posix": {"gen_args": False, "query": False},
"nomembers": {"query": False},
# Module-level params (not per-item, checked via self.params)
"query_param": {"module_param": True},
}
QUERY_FIELDS = {
"prefix": "groups",
"primary_key": "cn",
"base": ["name", "description", "gid"]
}
def main(): def main():
group_spec = dict( group_spec = dict(
# present # present
@@ -604,11 +475,6 @@ def main():
rename=dict(type="str", required=False, default=None, rename=dict(type="str", required=False, default=None,
aliases=["new_name"]), aliases=["new_name"]),
) )
query_param_settings = IPAAnsibleModule.build_query_param_settings(
PARAM_MAPPING, QUERY_FIELDS
)
ansible_module = IPAAnsibleModule( ansible_module = IPAAnsibleModule(
argument_spec=dict( argument_spec=dict(
# general # general
@@ -625,16 +491,11 @@ def main():
), ),
elements='dict', elements='dict',
required=False), required=False),
# query
query_param=dict(type="list", elements="str", default=None,
choices=["ALL", "BASE", "PKEY_ONLY"]
+ query_param_settings["ALL"],
required=False),
# general # general
action=dict(type="str", default="group", action=dict(type="str", default="group",
choices=["member", "group"]), choices=["member", "group"]),
state=dict(type="str", default="present", state=dict(type="str", default="present",
choices=["present", "absent", "renamed", "query"]), choices=["present", "absent", "renamed"]),
# Add group specific parameters for simple use case # Add group specific parameters for simple use case
**group_spec **group_spec
@@ -643,6 +504,7 @@ def main():
# same time # same time
mutually_exclusive=[['posix', 'nonposix', 'external'], mutually_exclusive=[['posix', 'nonposix', 'external'],
["name", "groups"]], ["name", "groups"]],
required_one_of=[["name", "groups"]],
supports_check_mode=True, supports_check_mode=True,
) )
@@ -654,27 +516,34 @@ def main():
names = ansible_module.params_get("name") names = ansible_module.params_get("name")
groups = ansible_module.params_get("groups") groups = ansible_module.params_get("groups")
# query # present
query_param = ansible_module.params_get("query_param") description = ansible_module.params_get("description")
gid = ansible_module.params_get("gid")
nonposix = ansible_module.params_get("nonposix")
external = ansible_module.params_get("external")
idoverrideuser = ansible_module.params_get("idoverrideuser")
posix = ansible_module.params_get("posix")
nomembers = ansible_module.params_get("nomembers")
user = ansible_module.params_get_lowercase("user")
group = ansible_module.params_get_lowercase("group")
# Services are not case sensitive
service = ansible_module.params_get_lowercase("service")
membermanager_user = (
ansible_module.params_get_lowercase("membermanager_user"))
membermanager_group = (
ansible_module.params_get_lowercase("membermanager_group"))
externalmember = ansible_module.params_get("externalmember")
# rename
rename = ansible_module.params_get("rename")
# state and action # state and action
action = ansible_module.params_get("action") action = ansible_module.params_get("action")
state = ansible_module.params_get("state") state = ansible_module.params_get("state")
# Check parameters # Check parameters
if state != "query": if (names is None or len(names) < 1) and \
if (names is None or len(names) < 1) and \ (groups is None or len(groups) < 1):
(groups is None or len(groups) < 1): ansible_module.fail_json(msg="At least one name or groups is required")
ansible_module.fail_json(
msg="At least one name or groups is required")
else:
if action == "member":
ansible_module.fail_json(
msg="Query is not possible with action=member")
if groups is not None:
ansible_module.fail_json(
msg="groups can not be used with state=query, "
"use name instead")
if state in ["present", "renamed"]: if state in ["present", "renamed"]:
if names is not None and len(names) != 1: if names is not None and len(names) != 1:
@@ -682,6 +551,12 @@ def main():
ansible_module.fail_json( ansible_module.fail_json(
msg="Only one group can be %s at a time using 'name'." % what) msg="Only one group can be %s at a time using 'name'." % what)
check_parameters(ansible_module, state, action)
if external is False:
ansible_module.fail_json(
msg="group can not be non-external")
# Ensuring (adding) several groups with mixed types external, nonposix # Ensuring (adding) several groups with mixed types external, nonposix
# and posix require to have a fix in IPA: # and posix require to have a fix in IPA:
# #
@@ -705,6 +580,15 @@ def main():
"supported by your IPA version: " "supported by your IPA version: "
"https://pagure.io/freeipa/issue/9349") "https://pagure.io/freeipa/issue/9349")
if (
(externalmember is not None
or idoverrideuser is not None)
and context == "client"
):
ansible_module.fail_json(
msg="Cannot use externalmember in client context."
)
# Use groups if names is None # Use groups if names is None
if groups is not None: if groups is not None:
names = groups names = groups
@@ -714,24 +598,35 @@ def main():
changed = False changed = False
exit_args = {} exit_args = {}
# If nonposix is used, set posix as not nonposix
if nonposix is not None:
posix = not nonposix
# Connect to IPA API # Connect to IPA API
with ansible_module.ipa_connect(context=context): with ansible_module.ipa_connect(context=context):
if state == "query":
exit_args = ansible_module.execute_query(
names, query_param, group_find, query_param_settings,
convert_result=lambda res: query_convert_result(
ansible_module, res)
)
ansible_module.exit_json(changed=False, group=exit_args)
has_add_member_service = ansible_module.ipa_command_param_exists( has_add_member_service = ansible_module.ipa_command_param_exists(
"group_add_member", "service") "group_add_member", "service")
if service is not None and not has_add_member_service:
ansible_module.fail_json(
msg="Managing a service as part of a group is not supported "
"by your IPA version")
has_add_membermanager = ansible_module.ipa_command_exists( has_add_membermanager = ansible_module.ipa_command_exists(
"group_add_member_manager") "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"
)
has_idoverrideuser = api_check_param( has_idoverrideuser = api_check_param(
"group_add_member", "idoverrideuser") "group_add_member", "idoverrideuser")
if idoverrideuser is not None and not has_idoverrideuser:
ansible_module.fail_json(
msg="Managing a idoverrideuser as part of a group is not "
"supported by your IPA version")
commands = [] commands = []
group_set = set() group_set = set()
@@ -743,9 +638,42 @@ def main():
ansible_module.fail_json( ansible_module.fail_json(
msg="group '%s' is used more than once" % name) msg="group '%s' is used more than once" % name)
group_set.add(name) group_set.add(name)
# present
description = group_name.get("description")
gid = group_name.get("gid")
nonposix = group_name.get("nonposix")
external = group_name.get("external")
idoverrideuser = group_name.get("idoverrideuser")
posix = group_name.get("posix")
# Check mutually exclusive condition for multiple groups
# creation. It's not possible to check it with
# `mutually_exclusive` argument in `IPAAnsibleModule` class
# because it accepts only (list[str] or list[list[str]]). Here
# we need to loop over all groups and fail on mutually
# exclusive ones.
if all((posix, nonposix)) or\
all((posix, external)) or\
all((nonposix, external)):
ansible_module.fail_json(
msg="parameters are mutually exclusive for group "
"`{0}`: posix|nonposix|external".format(name))
# Duplicating the condition for multiple group creation
if external is False:
ansible_module.fail_json(
msg="group can not be non-external")
# If nonposix is used, set posix as not nonposix
if nonposix is not None:
posix = not nonposix
user = group_name.get("user")
group = group_name.get("group")
service = group_name.get("service")
membermanager_user = group_name.get("membermanager_user")
membermanager_group = group_name.get("membermanager_group")
externalmember = group_name.get("externalmember")
nomembers = group_name.get("nomembers")
rename = group_name.get("rename")
group_params = IPAAnsibleModule.extract_params_from_entry( check_parameters(ansible_module, state, action)
group_name, PARAM_MAPPING)
elif ( elif (
isinstance( isinstance(
@@ -753,54 +681,17 @@ def main():
) )
): ):
name = group_name name = group_name
group_params = IPAAnsibleModule.extract_params(
ansible_module, PARAM_MAPPING)
else: else:
ansible_module.fail_json(msg="Group '%s' is not valid" % ansible_module.fail_json(msg="Group '%s' is not valid" %
repr(group_name)) repr(group_name))
# Never reached, just added to make pylint happy
name = None
group_params = {}
check_parameters(ansible_module, state, action, group_params)
convert_params(ansible_module, group_params)
rename = group_params.get("rename")
posix = group_params.get("posix")
external = group_params.get("external")
# Check API capability for params used
if group_params.get("service") is not None \
and not has_add_member_service:
ansible_module.fail_json(
msg="Managing a service as part of a group is not "
"supported by your IPA version")
if (group_params.get("membermanager_user") is not None
or group_params.get("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")
if group_params.get("idoverrideuser") is not None \
and not has_idoverrideuser:
ansible_module.fail_json(
msg="Managing a idoverrideuser as part of a group is not "
"supported by your IPA version")
if (group_params.get("externalmember") is not None
or group_params.get("idoverrideuser") is not None) \
and context == "client":
ansible_module.fail_json(
msg="Cannot use externalmember in client context.")
# Make sure group exists # Make sure group exists
res_find = group_show(ansible_module, name) res_find = find_group(ansible_module, name)
# external members must be handled as SID # external members must de handled as SID
externalmember = convert_to_sid( externalmember = convert_to_sid(externalmember)
group_params.get("externalmember"))
# idoverrides need to be compared through SID # idoverrides need to be compared through SID
idoverrideuser = group_params.get("idoverrideuser")
idoverrideuser_sid = convert_to_sid(idoverrideuser) idoverrideuser_sid = convert_to_sid(idoverrideuser)
res_idoverrideuser_sid = convert_to_sid( res_idoverrideuser_sid = convert_to_sid(
(res_find or {}).get("member_idoverrideuser", [])) (res_find or {}).get("member_idoverrideuser", []))
@@ -814,8 +705,13 @@ def main():
) )
) )
user_add, user_del = [], []
group_add, group_del = [], []
service_add, service_del = [], []
externalmember_add, externalmember_del = [], [] externalmember_add, externalmember_del = [], []
idoverrides_add, idoverrides_del = [], [] idoverrides_add, idoverrides_del = [], []
membermanager_user_add, membermanager_user_del = [], []
membermanager_group_add, membermanager_group_del = [], []
# Create command # Create command
if state == "present": if state == "present":
@@ -824,8 +720,7 @@ def main():
external) external)
# Generate args # Generate args
args = IPAAnsibleModule.gen_args_from_mapping( args = gen_args(description, gid, nomembers)
PARAM_MAPPING, group_params)
if action == "group": if action == "group":
# Found the group # Found the group
@@ -864,10 +759,89 @@ def main():
classes.append("posixgroup") classes.append("posixgroup")
res_find["objectclass"] = classes res_find["objectclass"] = classes
member_args = gen_member_args(
user, group, service, externalmember, idoverrideuser
)
if not compare_args_ipa(ansible_module, member_args,
res_find):
# Generate addition and removal lists
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"))
(externalmember_add,
externalmember_del) = gen_add_del_lists(
externalmember, (
list(res_find.get("member_external", []))
+ list(res_find.get("ipaexternalmember", []))
)
)
# There are multiple ways to name an AD User, and any
# can be used in idoverrides, so we create the add/del
# lists based on SID, and then use the given user name
# to the idoverride.
(idoverrides_add,
idoverrides_del) = gen_add_del_lists(
idoverrideuser_sid, res_idoverrideuser_sid)
idoverrides_add = [
idoverride_set[sid] for sid in set(idoverrides_add)
]
idoverrides_del = [
idoverride_set[sid] for sid in set(idoverrides_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")
)
elif action == "member": elif action == "member":
if res_find is None: if res_find is None:
ansible_module.fail_json(msg="No group '%s'" % name) ansible_module.fail_json(msg="No group '%s'" % name)
# Reduce add lists for member_user, member_group,
# member_service and member_external to new entries
# only that are not in res_find.
user_add = gen_add_list(
user, res_find.get("member_user"))
group_add = gen_add_list(
group, res_find.get("member_group"))
service_add = gen_add_list(
service, res_find.get("member_service"))
externalmember_add = gen_add_list(
externalmember, (
list(res_find.get("member_external", []))
+ list(res_find.get("ipaexternalmember", []))
)
)
idoverrides_add = gen_add_list(
idoverrideuser_sid, res_idoverrideuser_sid)
idoverrides_add = [
idoverride_set[sid] for sid in set(idoverrides_add)
]
membermanager_user_add = gen_add_list(
membermanager_user,
res_find.get("membermanager_user")
)
membermanager_group_add = gen_add_list(
membermanager_group,
res_find.get("membermanager_group")
)
elif state == "absent": elif state == "absent":
if action == "group": if action == "group":
if res_find is not None: if res_find is not None:
@@ -877,6 +851,36 @@ def main():
if res_find is None: if res_find is None:
ansible_module.fail_json(msg="No group '%s'" % name) ansible_module.fail_json(msg="No group '%s'" % name)
if not is_external_group(res_find) and externalmember:
ansible_module.fail_json(
msg="Cannot add external members to a "
"non-external group."
)
user_del = gen_intersection_list(
user, res_find.get("member_user"))
group_del = gen_intersection_list(
group, res_find.get("member_group"))
service_del = gen_intersection_list(
service, res_find.get("member_service"))
externalmember_del = gen_intersection_list(
externalmember, (
list(res_find.get("member_external", []))
+ list(res_find.get("ipaexternalmember", []))
)
)
idoverrides_del = gen_intersection_list(
idoverrideuser_sid, res_idoverrideuser_sid)
idoverrides_del = [
idoverride_set[sid] for sid in set(idoverrides_del)
]
membermanager_user_del = gen_intersection_list(
membermanager_user, res_find.get("membermanager_user"))
membermanager_group_del = gen_intersection_list(
membermanager_group,
res_find.get("membermanager_group")
)
elif state == "renamed": elif state == "renamed":
if res_find is None: if res_find is None:
ansible_module.fail_json(msg="No group '%s'" % name) ansible_module.fail_json(msg="No group '%s'" % name)
@@ -885,71 +889,6 @@ def main():
else: else:
ansible_module.fail_json(msg="Unkown state '%s'" % state) ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Compute member add/del lists for standard members
if not has_add_member_service:
group_params["service"] = None
if not has_add_membermanager:
group_params["membermanager_user"] = None
group_params["membermanager_group"] = None
member_lists = gen_member_add_del_lists(
PARAM_MAPPING, group_params,
res_find or {}, action, state)
user_add, user_del = member_lists.get(
"user", ([], []))
group_add, group_del = member_lists.get(
"group", ([], []))
service_add, service_del = member_lists.get(
"service", ([], []))
membermanager_user_add, membermanager_user_del = member_lists.get(
"membermanager_user", ([], []))
(membermanager_group_add,
membermanager_group_del) = member_lists.get(
"membermanager_group", ([], []))
# Compute externalmember add/del lists
# (merges two res_find keys, can't use gen_member_add_del_lists)
existing_external = (
list(res_find.get("member_external", []))
+ list(res_find.get("ipaexternalmember", []))
) if res_find else []
if state == "present" and action != "member":
externalmember_add, externalmember_del = \
gen_add_del_lists(externalmember, existing_external)
elif state == "present" and action == "member":
externalmember_add = gen_add_list(
externalmember, existing_external)
externalmember_del = []
elif state == "absent" and action == "member":
externalmember_add = []
externalmember_del = gen_intersection_list(
externalmember, existing_external)
else:
externalmember_add = []
externalmember_del = []
# Compute idoverrideuser add/del lists
# (SID-based comparison, can't use gen_member_add_del_lists)
if state == "present" and action != "member":
idoverrides_add, idoverrides_del = gen_add_del_lists(
idoverrideuser_sid, res_idoverrideuser_sid)
elif state == "present" and action == "member":
idoverrides_add = gen_add_list(
idoverrideuser_sid, res_idoverrideuser_sid)
idoverrides_del = []
elif state == "absent" and action == "member":
idoverrides_add = []
idoverrides_del = gen_intersection_list(
idoverrideuser_sid, res_idoverrideuser_sid)
else:
idoverrides_add = []
idoverrides_del = []
idoverrides_add = [
idoverride_set[sid] for sid in set(idoverrides_add)
]
idoverrides_del = [
idoverride_set[sid] for sid in set(idoverrides_del)
]
# manage members # manage members
# setup member args for add/remove members. # setup member args for add/remove members.
add_member_args = { add_member_args = {

View File

@@ -184,7 +184,7 @@ options:
type: list type: list
elements: str elements: str
aliases: ["krbprincipalauthind"] aliases: ["krbprincipalauthind"]
choices: ["radius", "otp", "pkinit", "hardened", "idp", "passkey", ""] choices: ["radius", "otp", "pkinit", "hardened", "idp", ""]
required: false required: false
requires_pre_auth: requires_pre_auth:
description: Pre-authentication is required for the service description: Pre-authentication is required for the service
@@ -356,7 +356,7 @@ options:
type: list type: list
elements: str elements: str
aliases: ["krbprincipalauthind"] aliases: ["krbprincipalauthind"]
choices: ["radius", "otp", "pkinit", "hardened", "idp", "passkey", ""] choices: ["radius", "otp", "pkinit", "hardened", "idp", ""]
required: false required: false
requires_pre_auth: requires_pre_auth:
description: Pre-authentication is required for the service description: Pre-authentication is required for the service
@@ -758,7 +758,7 @@ def main():
auth_ind=dict(type='list', elements="str", auth_ind=dict(type='list', elements="str",
aliases=["krbprincipalauthind"], default=None, aliases=["krbprincipalauthind"], default=None,
choices=["radius", "otp", "pkinit", "hardened", "idp", choices=["radius", "otp", "pkinit", "hardened", "idp",
"passkey", ""]), ""]),
requires_pre_auth=dict(type="bool", aliases=["ipakrbrequirespreauth"], requires_pre_auth=dict(type="bool", aliases=["ipakrbrequirespreauth"],
default=None), default=None),
ok_as_delegate=dict(type="bool", aliases=["ipakrbokasdelegate"], ok_as_delegate=dict(type="bool", aliases=["ipakrbokasdelegate"],

View File

@@ -281,14 +281,6 @@ def main():
# Connect to IPA API # Connect to IPA API
with ansible_module.ipa_connect(): with ansible_module.ipa_connect():
# set required fields
required = ["base_id", "range_size"]
requires_baserid = (
ansible_module.ipa_command_param_exists("config_mod", "enable_sid")
and idrange_type in [None, "ipa-local"]
)
if requires_baserid:
required.extend(["rid_base", "secondary_rid_base"])
commands = [] commands = []
for name in names: for name in names:
@@ -329,18 +321,6 @@ def main():
del args["iparangetype"] del args["iparangetype"]
commands.append([name, "idrange_mod", args]) commands.append([name, "idrange_mod", args])
else: else:
# Check if required parameters were given
missing_params = [
pname for pname in required
if ansible_module.params_get(pname) is None
]
if missing_params:
ansible_module.fail_json(
msg=(
"Missing required parameters: %s"
% (", ".join(missing_params))
)
)
commands.append([name, "idrange_add", args]) commands.append([name, "idrange_add", args])
elif state == "absent": elif state == "absent":

View File

@@ -1,173 +0,0 @@
# -*- coding: utf-8 -*-
# Authors:
# Rafael Guterres Jeffman <rjeffman@redhat.com>
#
# Copyright (C) 2025 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.0",
"supported_by": "community",
"status": ["preview"],
}
DOCUMENTATION = """
---
module: ipapasskeyconfig
short_description: Manage FreeIPA passkeyconfig
description: Manage FreeIPA passkeyconfig
extends_documentation_fragment:
- ipamodule_base_docs
options:
require_user_verification:
description: Require user verification for passkey authentication
required: false
type: bool
aliases: ["iparequireuserverification"]
author:
- Rafael Guterres Jeffman (@rjeffman)
"""
EXAMPLES = """
# Set passkeyconfig
- ipapasskeyconfig:
ipaadmin_password: SomeADMINpassword
require_user_verification: false
# Get current passkeyconfig
- ipapasskeyconfig:
ipaadmin_password: SomeADMINpassword
"""
RETURN = """
passkeyconfig:
description: Dict of passkeyconfig settings
returned: always
type: dict
contains:
require_user_verification:
description: Require user verification for passkey authentication
type: bool
returned: always
"""
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, ipalib_errors
from ansible.module_utils import six
if six.PY3:
unicode = str
def find_passkeyconfig(module):
"""Find the current passkeyconfig settings."""
try:
_result = module.ipa_command_no_name(
"passkeyconfig_show", {"all": True})
except ipalib_errors.NotFound:
# An exception is raised if passkeyconfig is not found.
return None
return _result["result"]
def gen_args(require_user_verification):
_args = {}
if require_user_verification is not None:
_args["iparequireuserverification"] = require_user_verification
return _args
def main():
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# passkeyconfig
require_user_verification=dict(
required=False, type='bool',
aliases=["iparequireuserverification"],
default=None
),
),
supports_check_mode=True,
)
ansible_module._ansible_debug = True
# Get parameters
require_user_verification = (
ansible_module.params_get("require_user_verification")
)
# Init
changed = False
exit_args = {}
# Connect to IPA API
with ansible_module.ipa_connect():
if not ansible_module.ipa_command_exists("passkeyconfig_show"):
msg = "Managing passkeyconfig is not supported by your IPA version"
ansible_module.fail_json(msg=msg)
result = find_passkeyconfig(ansible_module)
if result is None:
ansible_module.fail_json(msg="Could not retrieve passkeyconfig")
if require_user_verification is not None:
# Generate args
args = gen_args(require_user_verification)
# Check if there are different settings in the find result.
# If yes: modify
if not compare_args_ipa(ansible_module, args, result):
changed = True
if not ansible_module.check_mode:
try:
ansible_module.ipa_command_no_name(
"passkeyconfig_mod", args)
except ipalib_errors.EmptyModlist:
changed = False
except Exception as e:
ansible_module.fail_json(
msg="passkeyconfig_mod failed: %s" % str(e))
else:
# No parameters provided, just return current config
pass
# Get updated config if changes were made
if changed:
result = find_passkeyconfig(ansible_module)
# Prepare exit args
exit_args["passkeyconfig"] = {}
if result:
# Map IPA API field to module parameter
if "iparequireuserverification" in result:
exit_args["passkeyconfig"]["require_user_verification"] = \
result["iparequireuserverification"][0]
# Done
ansible_module.exit_json(changed=changed, **exit_args)
if __name__ == "__main__":
main()

View File

@@ -85,11 +85,6 @@ options:
type: list type: list
elements: str elements: str
required: false required: false
sysaccount:
description: List of sysaccounts.
type: list
elements: str
required: false
action: action:
description: Work on role or member level. description: Work on role or member level.
type: str type: str
@@ -182,7 +177,7 @@ def check_parameters(module):
"description", "description",
"user", "group", "user", "group",
"host", "hostgroup", "host", "hostgroup",
"service", "sysaccount", "service",
"privilege", "privilege",
] ]
@@ -230,7 +225,7 @@ def ensure_absent_state(module, name, action, res_find):
{"privilege": del_list}]) {"privilege": del_list}])
member_args = {} member_args = {}
for key in ['user', 'group', 'hostgroup', 'sysaccount']: for key in ['user', 'group', 'hostgroup']:
_members = module.params_get_lowercase(key) _members = module.params_get_lowercase(key)
if _members: if _members:
del_list = gen_intersection_list( del_list = gen_intersection_list(
@@ -340,7 +335,7 @@ def ensure_role_with_members_is_present(module, name, res_find, action):
add_members = {} add_members = {}
del_members = {} del_members = {}
for key in ["user", "group", "hostgroup", "sysaccount"]: for key in ["user", "group", "hostgroup"]:
_members = module.params_get_lowercase(key) _members = module.params_get_lowercase(key)
if _members is not None: if _members is not None:
add_list, del_list = gen_add_del_lists( add_list, del_list = gen_add_del_lists(
@@ -442,8 +437,6 @@ def create_module():
default=None), default=None),
service=dict(required=False, type='list', elements="str", service=dict(required=False, type='list', elements="str",
default=None), default=None),
sysaccount=dict(required=False, type='list', elements="str",
default=None),
# state # state
action=dict(type="str", default="role", action=dict(type="str", default="role",
@@ -474,15 +467,8 @@ def main():
state = ansible_module.params_get("state") state = ansible_module.params_get("state")
action = ansible_module.params_get("action") action = ansible_module.params_get("action")
names = ansible_module.params_get("name") names = ansible_module.params_get("name")
sysaccount = ansible_module.params_get("sysaccount")
commands = [] commands = []
has_sysaccount_member = ansible_module.ipa_command_param_exists(
"role_add_member", "sysaccount")
if not has_sysaccount_member and sysaccount is not None:
ansible_module.fail_json(
msg="sysaccount members are not supported by your IPA version")
for name in names: for name in names:
cmds = role_commands_for_name(ansible_module, state, action, name) cmds = role_commands_for_name(ansible_module, state, action, name)
commands.extend(cmds) commands.extend(cmds)

View File

@@ -74,7 +74,7 @@ options:
type: list type: list
elements: str elements: str
required: false required: false
choices: ["otp", "radius", "pkinit", "hardened", "idp", "passkey", ""] choices: ["otp", "radius", "pkinit", "hardened", "idp", ""]
aliases: ["krbprincipalauthind"] aliases: ["krbprincipalauthind"]
skip_host_check: skip_host_check:
description: Skip checking if host object exists. description: Skip checking if host object exists.
@@ -192,7 +192,7 @@ options:
type: list type: list
elements: str elements: str
required: false required: false
choices: ["otp", "radius", "pkinit", "hardened", "idp", "passkey", ""] choices: ["otp", "radius", "pkinit", "hardened", "idp", ""]
aliases: ["krbprincipalauthind"] aliases: ["krbprincipalauthind"]
skip_host_check: skip_host_check:
description: Skip checking if host object exists. description: Skip checking if host object exists.
@@ -560,7 +560,7 @@ def init_ansible_module():
auth_ind=dict(type="list", elements="str", auth_ind=dict(type="list", elements="str",
aliases=["krbprincipalauthind"], aliases=["krbprincipalauthind"],
choices=["otp", "radius", "pkinit", "hardened", "idp", choices=["otp", "radius", "pkinit", "hardened", "idp",
"passkey", ""]), ""]),
skip_host_check=dict(type="bool"), skip_host_check=dict(type="bool"),
force=dict(type="bool"), force=dict(type="bool"),
requires_pre_auth=dict( requires_pre_auth=dict(

View File

@@ -51,7 +51,8 @@ options:
suboptions: suboptions:
name: name:
description: The sudorule name description: The sudorule name
type: str type: list
elements: str
required: true required: true
aliases: ["cn"] aliases: ["cn"]
description: description:
@@ -461,7 +462,7 @@ def init_ansible_module():
required=False), required=False),
sudorules=dict( sudorules=dict(
type="list", type="list",
default=None, defalut=None,
options=dict( options=dict(
# name of the sudorule # name of the sudorule
name=dict(type="str", required=True, aliases=["cn"]), name=dict(type="str", required=True, aliases=["cn"]),

View File

@@ -1,309 +0,0 @@
# -*- coding: utf-8 -*-
# Authors:
# Thomas Woerner <twoerner@redhat.com>
#
# Copyright (C) 2025 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.0",
"supported_by": "community",
"status": ["preview"],
}
DOCUMENTATION = """
---
module: ipasysaccount
short_description: Manage FreeIPA system account
description: Manage FreeIPA system account
extends_documentation_fragment:
- ipamodule_base_docs
- ipamodule_base_docs.delete_continue
options:
name:
description: The list of sysaccount name strings (internally uid).
required: true
type: list
elements: str
aliases: ["login"]
description:
description: A description for the sysaccount.
type: str
required: false
privileged:
description: Allow password updates without reset.
type: bool
required: false
random:
description: Generate a random user password.
required: false
type: bool
password:
description: Set the user password.
required: false
type: str
aliases: ["userpassword"]
update_password:
description:
Set password for a sysaccount in present state only on creation or always
type: str
choices: ["always", "on_create"]
required: false
state:
description: The state to ensure.
choices: ["present", "absent", "enabled", "disabled"]
default: present
type: str
author:
- Thomas Woerner (@t-woerner)
"""
EXAMPLES = """
# Ensure sysaccount my-app is present
- ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
random: true
# Ensure sysaccount my-app is absent
- ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
state: absent
# Ensure existing sysaccount my-app is privileged
- ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
privileged: true
# Ensure existing sysaccount my-app is not privileged
- ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
privileged: false
# Ensure existing sysaccount my-app is disabled
- ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
state: disabled
# Ensure existing sysaccount my-app is enabled
- ipasysaccount:
ipaadmin_password: SomeADMINpassword
name: my-app
state: enabled
"""
RETURN = """
sysaccount:
description: Sysaccount dict with random password
returned: |
If random is yes and user sysaccount not exist or update_password is yes
type: dict
contains:
randompassword:
description: The generated random password
type: str
"""
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, ipalib_errors
from ansible.module_utils import six
if six.PY3:
unicode = str
def find_sysaccount(module, name):
"""Find if a sysaccount with the given name already exist."""
try:
_result = module.ipa_command("sysaccount_show", name, {"all": True})
except ipalib_errors.NotFound:
# An exception is raised if sysaccount name is not found.
return None
return _result["result"]
def gen_args(description, random, privileged, password):
_args = {}
if description is not None:
_args["description"] = description
if random is not None:
_args["random"] = random
if privileged is not None:
_args["privileged"] = privileged
if password is not None:
_args["userpassword"] = password
return _args
# pylint: disable=unused-argument
def result_handler(module, result, command, name, args, exit_args, errors):
if "random" in args and command in ["sysaccount_add", "sysaccount_mod"] \
and "randompassword" in result["result"]:
exit_args["randompassword"] = \
result["result"]["randompassword"]
def main():
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# general
name=dict(type="list", elements="str", required=True,
aliases=["login"]),
# present
description=dict(required=False, type='str', default=None),
random=dict(required=False, type='bool', default=None),
privileged=dict(required=False, type='bool', default=None),
password=dict(required=False, type='str', no_log=True,
aliases=["userpassword"], default=None),
# mod
update_password=dict(type='str', default=None, no_log=False,
choices=['always', 'on_create']),
# state
state=dict(type="str", default="present",
choices=["present", "absent", "enabled", "disabled"]),
),
supports_check_mode=True,
ipa_module_options=["delete_continue"],
mutually_exclusive=[["random", "password"]]
)
ansible_module._ansible_debug = True
# Get parameters
# general
names = ansible_module.params_get("name")
# present
description = ansible_module.params_get("description")
random = ansible_module.params_get("random")
privileged = ansible_module.params_get("privileged")
password = ansible_module.params_get("password")
# mod
update_password = ansible_module.params_get("update_password")
# absent
delete_continue = ansible_module.params_get("delete_continue")
# state
state = ansible_module.params_get("state")
# Check parameters
invalid = []
if state == "present" and len(names) != 1:
ansible_module.fail_json(
msg="Only one sysaccount can be added at a time.")
if state in ["absent", "enabled", "disabled"]:
if len(names) < 1:
ansible_module.fail_json(msg="No name given.")
invalid = ["description", "random", "privileged", "password"]
ansible_module.params_fail_used_invalid(invalid, state)
# Init
changed = False
exit_args = {}
# Connect to IPA API
with ansible_module.ipa_connect():
if not ansible_module.ipa_command_exists("sysaccount_add"):
ansible_module.fail_json(
msg=("Managing sysaccounts is not supported by your "
"IPA version")
)
commands = []
for name in names:
# Make sure sysaccount exists
res_find = find_sysaccount(ansible_module, name)
# Create command
if state == "present":
# Generate args
args = gen_args(description, random, privileged, password)
# Found the sysaccount
if res_find is not None:
# Ignore password and random with
# update_password == on_create
if update_password == "on_create":
if "userpassword" in args:
del args["userpassword"]
if "random" in args:
del args["random"]
# if using "random:false" password should not be
# generated.
if not args.get("random", True):
del args["random"]
# For all settings is args, check if there are
# different settings in the find result.
# If yes: modify
if not compare_args_ipa(ansible_module, args,
res_find):
commands.append([name, "sysaccount_mod", args])
else:
commands.append([name, "sysaccount_add", args])
elif state == "absent":
if res_find is not None:
commands.append(
[name, "sysaccount_del", {"continue": delete_continue}]
)
elif state == "enabled":
if res_find is not None and res_find["nsaccountlock"]:
commands.append([name, "sysaccount_enable", {}])
elif state == "disabled":
if res_find is not None and not res_find["nsaccountlock"]:
commands.append([name, "sysaccount_disable", {}])
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Execute commands
changed = ansible_module.execute_ipa_commands(
commands, result_handler, keeponly=["randompassword"],
exit_args=exit_args)
# Done
ansible_module.exit_json(changed=changed, sysaccount=exit_args)
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

View File

@@ -4,4 +4,3 @@ junit_family = xunit1
markers= markers=
source_order: mark test as order bound source_order: mark test as order bound
playbook: playbook tests playbook: playbook tests
pythonpath = tests

View File

@@ -1,7 +1,7 @@
-r requirements-tests.txt -r requirements-tests.txt
ipdb==0.13.4 ipdb==0.13.4
pre-commit==2.20.0 pre-commit==2.20.0
flake8 flake8==7.0.0
flake8-bugbear flake8-bugbear
pylint>=3.2 pylint>=3.2
wrapt==1.14.1 wrapt==1.14.1

View File

@@ -1,8 +1,8 @@
-r requirements.txt -r requirements.txt
pytest pytest==7.1.3
pytest-sourceorder pytest-sourceorder==0.6.0
pytest-split>=0.8.0 pytest-split>=0.8.0
pytest-custom_exit_code>=0.3.0 pytest-custom_exit_code>=0.3.0
pytest-testinfra pytest-testinfra==6.8.0
pytest-randomly pytest-randomly==3.12.0
pyyaml>=3 pyyaml>=3

View File

@@ -1 +0,0 @@
setuptools

View File

@@ -91,21 +91,20 @@
enabled: yes enabled: yes
state: started state: started
- name: Firewalld - Verify zones - name: Firewalld - Verify runtime zone "{{ ipabackup_firewalld_zone }}"
ansible.builtin.shell: >
firewall-cmd
--info-zone="{{ ipabackup_firewalld_zone }}"
>/dev/null
when: ipabackup_firewalld_zone is defined when: ipabackup_firewalld_zone is defined
block:
- name: Firewalld - Verify runtime zone from ipabackup_firewalld_zone
ansible.builtin.shell: >
firewall-cmd
--info-zone="{{ ipabackup_firewalld_zone }}"
>/dev/null
- name: Firewalld - Verify permanent zone from ipabackup_firewalld_zone - name: Firewalld - Verify permanent zone "{{ ipabackup_firewalld_zone }}"
ansible.builtin.shell: > ansible.builtin.shell: >
firewall-cmd firewall-cmd
--permanent --permanent
--info-zone="{{ ipabackup_firewalld_zone }}" --info-zone="{{ ipabackup_firewalld_zone }}"
>/dev/null >/dev/null
when: ipabackup_firewalld_zone is defined
### RESTORE ### RESTORE

View File

@@ -202,8 +202,6 @@ Variable | Description | Required
`ipaclient_request_cert` | The bool value defines if the certificate for the machine wil be requested. The certificate will be stored in /etc/ipa/nssdb under the nickname "Local IPA host". . `ipaclient_request_cert` defaults to `no`. The option is deprecated and will be removed in a future release. | no `ipaclient_request_cert` | The bool value defines if the certificate for the machine wil be requested. The certificate will be stored in /etc/ipa/nssdb under the nickname "Local IPA host". . `ipaclient_request_cert` defaults to `no`. The option is deprecated and will be removed in a future release. | no
`ipaclient_keytab` | The string value contains the path on the node of a backup host keytab from a previous enrollment. | no `ipaclient_keytab` | The string value contains the path on the node of a backup host keytab from a previous enrollment. | no
`ipaclient_automount_location` | Automount location | no `ipaclient_automount_location` | Automount location | no
`ipaclient_dns_over_tls` | Configure DNS over TLS. Requires FreeIPA version 4.12.5 or later. (bool, default: false) | no
`ipaclient_no_dnssec_validation` | Disable DNSSEC validation for DNS over TLS. This turns off DNSSEC validation for unbound. Ignored if `ipaserver_dns_over_tls` is not enabled. (bool, default: false) | no
Server Variables Server Variables

View File

@@ -26,8 +26,6 @@ ipasssd_enable_dns_updates: no
ipasssd_no_krb5_offline_passwords: no ipasssd_no_krb5_offline_passwords: no
ipasssd_preserve_sssd: no ipasssd_preserve_sssd: no
ipaclient_request_cert: no ipaclient_request_cert: no
ipaclient_dns_over_tls: no
ipaclient_no_dnssec_validation: no
### packages ### ### packages ###
ipaclient_install_packages: yes ipaclient_install_packages: yes

View File

@@ -86,16 +86,6 @@ options:
type: bool type: bool
required: no required: no
default: no default: no
dns_over_tls:
description: Configure DNS over TLS
type: bool
default: no
required: no
no_dnssec_validation:
description: Disable DNSSEC validation for DNS over TLS
type: bool
default: no
required: no
enable_dns_updates: enable_dns_updates:
description: | description: |
Configures the machine to attempt dns updates when the ip address Configures the machine to attempt dns updates when the ip address
@@ -222,9 +212,7 @@ def main():
mkhomedir=dict(required=False, type='bool'), mkhomedir=dict(required=False, type='bool'),
on_master=dict(required=False, type='bool'), on_master=dict(required=False, type='bool'),
dnsok=dict(required=False, type='bool', default=False), dnsok=dict(required=False, type='bool', default=False),
dns_over_tls=dict(required=False, type='bool', default=False),
no_dnssec_validation=dict(required=False, type='bool',
default=False),
enable_dns_updates=dict(required=False, type='bool'), enable_dns_updates=dict(required=False, type='bool'),
all_ip_addresses=dict(required=False, type='bool', default=False), all_ip_addresses=dict(required=False, type='bool', default=False),
ip_addresses=dict(required=False, type='list', elements='str', ip_addresses=dict(required=False, type='list', elements='str',
@@ -261,8 +249,6 @@ def main():
options.mkhomedir = module.params.get('mkhomedir') options.mkhomedir = module.params.get('mkhomedir')
options.on_master = module.params.get('on_master') options.on_master = module.params.get('on_master')
dnsok = module.params.get('dnsok') dnsok = module.params.get('dnsok')
options.dns_over_tls = module.params.get('dns_over_tls')
options.no_dnssec_validation = module.params.get('no_dnssec_validation')
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE) fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE) statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE)
@@ -270,7 +256,6 @@ def main():
os.environ['KRB5CCNAME'] = paths.IPA_DNS_CCACHE os.environ['KRB5CCNAME'] = paths.IPA_DNS_CCACHE
options.dns_updates = module.params.get('enable_dns_updates') options.dns_updates = module.params.get('enable_dns_updates')
options.dns_over_tls = module.params.get('dns_over_tls')
options.all_ip_addresses = module.params.get('all_ip_addresses') options.all_ip_addresses = module.params.get('all_ip_addresses')
options.ip_addresses = ansible_module_get_parsed_ip_addresses(module) options.ip_addresses = ansible_module_get_parsed_ip_addresses(module)
options.request_cert = module.params.get('request_cert') options.request_cert = module.params.get('request_cert')
@@ -294,7 +279,6 @@ def main():
options.no_sssd = False options.no_sssd = False
options.sssd = not options.no_sssd options.sssd = not options.no_sssd
options.no_ac = False options.no_ac = False
options.dns_over_tls = module.params.get('dns_over_tls')
nosssd_files = module.params.get('nosssd_files') nosssd_files = module.params.get('nosssd_files')
selinux_works = module.params.get('selinux_works') selinux_works = module.params.get('selinux_works')
krb_name = module.params.get('krb_name') krb_name = module.params.get('krb_name')
@@ -355,19 +339,17 @@ def main():
ca_subject) ca_subject)
ca_certs_trust = [(c, n, ca_certs_trust = [(c, n,
certstore.key_policy_to_trust_flags(t, True, u)) certstore.key_policy_to_trust_flags(t, True, u))
for (c, n, t, u) in [x[0:4] for x in ca_certs]] for (c, n, t, u) in ca_certs]
if hasattr(paths, "KDC_CA_BUNDLE_PEM"): if hasattr(paths, "KDC_CA_BUNDLE_PEM"):
x509.write_certificate_list( x509.write_certificate_list(
[c for c, n, t, u in [x[0:4] for x in ca_certs] [c for c, n, t, u in ca_certs if t is not False],
if t is not False],
paths.KDC_CA_BUNDLE_PEM, paths.KDC_CA_BUNDLE_PEM,
# mode=0o644 # mode=0o644
) )
if hasattr(paths, "CA_BUNDLE_PEM"): if hasattr(paths, "CA_BUNDLE_PEM"):
x509.write_certificate_list( x509.write_certificate_list(
[c for c, n, t, u in [x[0:4] for x in ca_certs] [c for c, n, t, u in ca_certs if t is not False],
if t is not False],
paths.CA_BUNDLE_PEM, paths.CA_BUNDLE_PEM,
# mode=0o644 # mode=0o644
) )
@@ -388,22 +370,13 @@ def main():
tasks.insert_ca_certs_into_systemwide_ca_store(ca_certs) tasks.insert_ca_certs_into_systemwide_ca_store(ca_certs)
if not options.on_master: if not options.on_master:
argspec_client_dns = getargspec(client_dns) client_dns(cli_server[0], hostname, options)
if "statestore" in argspec_client_dns.args:
client_dns(cli_server[0], hostname, options, statestore)
else:
client_dns(cli_server[0], hostname, options)
if hasattr(paths, "SSH_CONFIG_DIR"): if hasattr(paths, "SSH_CONFIG_DIR"):
ssh_config_dir = paths.SSH_CONFIG_DIR ssh_config_dir = paths.SSH_CONFIG_DIR
else: else:
ssh_config_dir = services.knownservices.sshd.get_config_dir() ssh_config_dir = services.knownservices.sshd.get_config_dir()
argspec_update_ssh_keys = getargspec(update_ssh_keys) update_ssh_keys(hostname, ssh_config_dir, options.create_sshfp)
# Hotfix for https://github.com/freeipa/freeipa/pull/7343
if "options" in argspec_update_ssh_keys.args:
update_ssh_keys(hostname, ssh_config_dir, options, cli_server[0])
else:
update_ssh_keys(hostname, ssh_config_dir, options.create_sshfp)
try: try:
os.remove(CCACHE_FILE) os.remove(CCACHE_FILE)

View File

@@ -91,11 +91,6 @@ options:
changes changes
type: bool type: bool
required: no required: no
dns_over_tls:
description: Configure DNS over TLS
type: bool
default: no
required: no
preserve_sssd: preserve_sssd:
description: Preserve old SSSD configuration if possible description: Preserve old SSSD configuration if possible
type: bool type: bool
@@ -145,7 +140,6 @@ def main():
fixed_primary=dict(required=False, type='bool'), fixed_primary=dict(required=False, type='bool'),
permit=dict(required=False, type='bool'), permit=dict(required=False, type='bool'),
enable_dns_updates=dict(required=False, type='bool'), enable_dns_updates=dict(required=False, type='bool'),
dns_over_tls=dict(required=False, type='bool', default=False),
preserve_sssd=dict(required=False, type='bool'), preserve_sssd=dict(required=False, type='bool'),
no_krb5_offline_passwords=dict(required=False, type='bool'), no_krb5_offline_passwords=dict(required=False, type='bool'),
), ),
@@ -175,13 +169,11 @@ def main():
options.primary = module.params.get('fixed_primary') options.primary = module.params.get('fixed_primary')
options.permit = module.params.get('permit') options.permit = module.params.get('permit')
options.dns_updates = module.params.get('enable_dns_updates') options.dns_updates = module.params.get('enable_dns_updates')
options.dns_over_tls = module.params.get('dns_over_tls')
options.preserve_sssd = module.params.get('preserve_sssd') options.preserve_sssd = module.params.get('preserve_sssd')
options.no_krb5_offline_passwords = module.params.get( options.no_krb5_offline_passwords = module.params.get(
'no_krb5_offline_passwords') 'no_krb5_offline_passwords')
options.krb5_offline_passwords = not options.no_krb5_offline_passwords options.krb5_offline_passwords = not options.no_krb5_offline_passwords
options.dns_over_tls = False
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE) fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
client_domain = hostname[hostname.find(".") + 1:] client_domain = hostname[hostname.find(".") + 1:]

View File

@@ -124,16 +124,6 @@ options:
type: bool type: bool
required: no required: no
default: no default: no
dns_over_tls:
description: Configure DNS over TLS
type: bool
default: no
required: no
no_dnssec_validation:
description: Disable DNSSEC validation for DNS over TLS
type: bool
default: no
required: no
enable_dns_updates: enable_dns_updates:
description: description:
Configures the machine to attempt dns updates when the ip address Configures the machine to attempt dns updates when the ip address
@@ -258,8 +248,7 @@ from ansible.module_utils.ansible_ipa_client import (
CLIENT_INSTALL_ERROR, tasks, check_ldap_conf, timeconf, constants, CLIENT_INSTALL_ERROR, tasks, check_ldap_conf, timeconf, constants,
validate_hostname, nssldap_exists, gssapi, remove_file, validate_hostname, nssldap_exists, gssapi, remove_file,
check_ip_addresses, ipadiscovery, print_port_conf_info, check_ip_addresses, ipadiscovery, print_port_conf_info,
IPA_PYTHON_VERSION, getargspec, services, IPA_PYTHON_VERSION, getargspec
CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION
) )
@@ -339,9 +328,6 @@ def main():
default=None), default=None),
all_ip_addresses=dict(required=False, type='bool', default=False), all_ip_addresses=dict(required=False, type='bool', default=False),
on_master=dict(required=False, type='bool', default=False), on_master=dict(required=False, type='bool', default=False),
dns_over_tls=dict(required=False, type='bool', default=False),
no_dnssec_validation=dict(required=False, type='bool',
default=False),
# sssd # sssd
enable_dns_updates=dict(required=False, type='bool', enable_dns_updates=dict(required=False, type='bool',
default=False), default=False),
@@ -370,8 +356,6 @@ def main():
options.ip_addresses = module.params.get('ip_addresses') options.ip_addresses = module.params.get('ip_addresses')
options.all_ip_addresses = module.params.get('all_ip_addresses') options.all_ip_addresses = module.params.get('all_ip_addresses')
options.on_master = module.params.get('on_master') options.on_master = module.params.get('on_master')
options.dns_over_tls = module.params.get('dns_over_tls')
options.no_dnssec_validation = module.params.get('no_dnssec_validation')
options.enable_dns_updates = module.params.get('enable_dns_updates') options.enable_dns_updates = module.params.get('enable_dns_updates')
# Get domain from first server if domain is not set, but if there are # Get domain from first server if domain is not set, but if there are
@@ -381,16 +365,6 @@ def main():
options.domain_name = options.servers[0][ options.domain_name = options.servers[0][
options.servers[0].find(".") + 1:] options.servers[0].find(".") + 1:]
if options.dns_over_tls \
and not services.knownservices["unbound"].is_installed():
module.fail_json(
msg="To enable DNS over TLS, package ipa-client-encrypted-dns "
"must be installed.")
if options.dns_over_tls and not CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION:
module.fail_json(
msg="Important patches for DNS over TLS are missing in your IPA "
"version.")
try: try:
self = options self = options
@@ -460,10 +434,9 @@ def main():
# ClientInstall # ClientInstall
if options.ca_cert_files is not None: if options.ca_cert_files is not None:
# ca_cert_files is always a list of strings or None,
# therefore no isinstance(options.ca_cert_files, list) test
# needed.
for value in options.ca_cert_files: for value in options.ca_cert_files:
if not isinstance(value, list):
raise ValueError("Expected list, got {0!r}".format(value))
# this is what init() does # this is what init() does
value = value[-1] value = value[-1]
if not os.path.exists(value): if not os.path.exists(value):

View File

@@ -23,9 +23,7 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
# pylint: disable=invalid-name
__metaclass__ = type __metaclass__ = type
# pylint: enable=invalid-name
__all__ = ["gssapi", "version", "ipadiscovery", "api", "errors", "x509", __all__ = ["gssapi", "version", "ipadiscovery", "api", "errors", "x509",
"constants", "sysrestore", "certmonger", "certstore", "constants", "sysrestore", "certmonger", "certstore",
@@ -51,8 +49,7 @@ __all__ = ["gssapi", "version", "ipadiscovery", "api", "errors", "x509",
"sssd_enable_ifp", "configure_selinux_for_client", "sssd_enable_ifp", "configure_selinux_for_client",
"getargspec", "paths", "options", "getargspec", "paths", "options",
"IPA_PYTHON_VERSION", "NUM_VERSION", "certdb", "get_ca_cert", "IPA_PYTHON_VERSION", "NUM_VERSION", "certdb", "get_ca_cert",
"ipalib", "logger", "ipautil", "installer", "ipalib", "logger", "ipautil", "installer"]
"CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION"]
import sys import sys
@@ -79,7 +76,6 @@ except ImportError:
return ArgSpec(args, varargs, varkw, defaults) return ArgSpec(args, varargs, varkw, defaults)
ANSIBLE_IPA_CLIENT_MODULE_IMPORT_ERROR = None # pylint: disable=invalid-name
try: try:
from ipapython.version import NUM_VERSION, VERSION from ipapython.version import NUM_VERSION, VERSION
@@ -235,6 +231,8 @@ try:
cli_realm, cli_domain, cli_server, cli_kdc, dnsok, cli_realm, cli_domain, cli_server, cli_kdc, dnsok,
filename, client_domain, client_hostname, force=False, filename, client_domain, client_hostname, force=False,
configure_sssd=True): configure_sssd=True):
# pylint: disable=global-variable-not-assigned
global options
options.force = force options.force = force
options.sssd = configure_sssd options.sssd = configure_sssd
return ipa_client_install.configure_krb5_conf( return ipa_client_install.configure_krb5_conf(
@@ -314,19 +312,6 @@ try:
except ImportError: except ImportError:
configure_selinux_for_client = None configure_selinux_for_client = None
try:
# pylint: disable=invalid-name
CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION = False
# pylint: enable=invalid-name
from ipaclient.install.client import ClientInstallInterface
except ImportError:
pass
else:
if hasattr(ClientInstallInterface, "no_dnssec_validation"):
# pylint: disable=invalid-name
CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION = True
# pylint: enable=invalid-name
logger = logging.getLogger("ipa-client-install") logger = logging.getLogger("ipa-client-install")
root_logger = logger root_logger = logger
@@ -335,13 +320,14 @@ try:
raise RuntimeError("freeipa version '%s' is too old" % VERSION) raise RuntimeError("freeipa version '%s' is too old" % VERSION)
except ImportError as _err: except ImportError as _err:
# pylint: disable=invalid-name
ANSIBLE_IPA_CLIENT_MODULE_IMPORT_ERROR = str(_err) ANSIBLE_IPA_CLIENT_MODULE_IMPORT_ERROR = str(_err)
# pylint: enable=invalid-name
for attr in __all__: for attr in __all__:
setattr(sys.modules[__name__], attr, None) setattr(sys.modules[__name__], attr, None)
else:
ANSIBLE_IPA_CLIENT_MODULE_IMPORT_ERROR = None
def setup_logging(): def setup_logging():
standard_logging_setup( standard_logging_setup(

View File

@@ -1,23 +1,11 @@
--- ---
# tasks file for ipaclient # tasks file for ipaclient
- name: Install - Package installation - name: Install - Ensure that IPA client packages are installed
ansible.builtin.package:
name: "{{ ipaclient_packages }}"
state: present
when: ipaclient_install_packages | bool when: ipaclient_install_packages | bool
block:
- name: Install - Set packages for installation
ansible.builtin.set_fact:
_ipapackages: "{{ ipaclient_packages }}"
- name: Install - Set packages for installlation, add DOT
ansible.builtin.set_fact:
_ipapackages: "{{ _ipapackages + ipaclient_packages_dot }}"
when: ipaclient_dns_over_tls | bool
- name: Install - Ensure that packages are installed
ansible.builtin.package:
name: "{{ _ipapackages }}"
state: present
- name: Install - Set ipaclient_servers - name: Install - Set ipaclient_servers
ansible.builtin.set_fact: ansible.builtin.set_fact:
@@ -50,7 +38,7 @@
msg: "ipaclient_domain or ipaserver_domain is required for ipaclient_configure_dns_resolver" msg: "ipaclient_domain or ipaserver_domain is required for ipaclient_configure_dns_resolver"
when: ipaserver_domain is not defined and ipaclient_domain is not defined when: ipaserver_domain is not defined and ipaclient_domain is not defined
- name: Install - Fail on missing ipaclient_dns_servers - name: Install - Fail on missing ipaclient_servers
ansible.builtin.fail: ansible.builtin.fail:
msg: "ipaclient_dns_servers is required for ipaclient_configure_dns_resolver" msg: "ipaclient_dns_servers is required for ipaclient_configure_dns_resolver"
when: ipaclient_dns_servers is not defined when: ipaclient_dns_servers is not defined
@@ -81,10 +69,9 @@
ip_addresses: "{{ ipaclient_ip_addresses | default(omit) }}" ip_addresses: "{{ ipaclient_ip_addresses | default(omit) }}"
all_ip_addresses: "{{ ipaclient_all_ip_addresses }}" all_ip_addresses: "{{ ipaclient_all_ip_addresses }}"
on_master: "{{ ipaclient_on_master }}" on_master: "{{ ipaclient_on_master }}"
dns_over_tls: "{{ ipaclient_dns_over_tls }}"
no_dnssec_validation: "{{ ipaclient_no_dnssec_validation }}"
### sssd ### ### sssd ###
enable_dns_updates: "{{ ipasssd_enable_dns_updates }}" enable_dns_updates: "{{ ipassd_enable_dns_updates
| default(ipasssd_enable_dns_updates) }}"
register: result_ipaclient_test register: result_ipaclient_test
- name: Install - Client deployment - name: Install - Client deployment
@@ -181,10 +168,10 @@
- name: Install - Store the previously obtained OTP - name: Install - Store the previously obtained OTP
no_log: yes no_log: yes
when: result_ipaclient_get_otp.host is defined
ansible.builtin.set_fact: ansible.builtin.set_fact:
ipaadmin_orig_password: "{{ ipaadmin_password | default(omit) }}" ipaadmin_orig_password: "{{ ipaadmin_password | default(omit) }}"
ipaadmin_password: "{{ result_ipaclient_get_otp.host.randompassword | default(omit) }}" ipaadmin_password: "{{ result_ipaclient_get_otp.host.randompassword
if result_ipaclient_get_otp.host is defined }}"
rescue: rescue:
- name: Install - Report error for OTP generation - name: Install - Report error for OTP generation
ansible.builtin.debug: ansible.builtin.debug:
@@ -334,12 +321,16 @@
no_sshd: "{{ ipaclient_no_sshd }}" no_sshd: "{{ ipaclient_no_sshd }}"
no_sudo: "{{ ipaclient_no_sudo }}" no_sudo: "{{ ipaclient_no_sudo }}"
all_ip_addresses: "{{ ipaclient_all_ip_addresses }}" all_ip_addresses: "{{ ipaclient_all_ip_addresses }}"
fixed_primary: "{{ ipasssd_fixed_primary }}" fixed_primary: "{{ ipassd_fixed_primary
permit: "{{ ipasssd_permit }}" | default(ipasssd_fixed_primary) }}"
enable_dns_updates: "{{ ipasssd_enable_dns_updates }}" permit: "{{ ipassd_permit | default(ipasssd_permit) }}"
dns_over_tls: "{{ ipaclient_dns_over_tls }}" enable_dns_updates: "{{ ipassd_enable_dns_updates
preserve_sssd: "{{ ipasssd_preserve_sssd }}" | default(ipasssd_enable_dns_updates) }}"
no_krb5_offline_passwords: "{{ ipasssd_no_krb5_offline_passwords }}" preserve_sssd: "{{ ipassd_preserve_sssd
| default(ipasssd_preserve_sssd) }}"
no_krb5_offline_passwords:
"{{ ipassd_no_krb5_offline_passwords
| default(ipasssd_no_krb5_offline_passwords) }}"
- name: Install - IPA API calls for remaining enrollment parts - name: Install - IPA API calls for remaining enrollment parts
ipaclient_api: ipaclient_api:
@@ -374,20 +365,23 @@
ca_enabled: "{{ result_ipaclient_api.ca_enabled }}" ca_enabled: "{{ result_ipaclient_api.ca_enabled }}"
on_master: "{{ ipaclient_on_master }}" on_master: "{{ ipaclient_on_master }}"
dnsok: "{{ result_ipaclient_test.dnsok }}" dnsok: "{{ result_ipaclient_test.dnsok }}"
enable_dns_updates: "{{ ipasssd_enable_dns_updates }}" enable_dns_updates: "{{ ipassd_enable_dns_updates
dns_over_tls: "{{ ipaclient_dns_over_tls }}" | default(ipasssd_enable_dns_updates) }}"
no_dnssec_validation: "{{ ipaclient_no_dnssec_validation }}"
all_ip_addresses: "{{ ipaclient_all_ip_addresses }}" all_ip_addresses: "{{ ipaclient_all_ip_addresses }}"
ip_addresses: "{{ ipaclient_ip_addresses | default(omit) }}" ip_addresses: "{{ ipaclient_ip_addresses | default(omit) }}"
request_cert: "{{ ipaclient_request_cert }}" request_cert: "{{ ipaclient_request_cert }}"
preserve_sssd: "{{ ipasssd_preserve_sssd }}" preserve_sssd: "{{ ipassd_preserve_sssd
| default(ipasssd_preserve_sssd) }}"
no_ssh: "{{ ipaclient_no_ssh }}" no_ssh: "{{ ipaclient_no_ssh }}"
no_sshd: "{{ ipaclient_no_sshd }}" no_sshd: "{{ ipaclient_no_sshd }}"
no_sudo: "{{ ipaclient_no_sudo }}" no_sudo: "{{ ipaclient_no_sudo }}"
subid: "{{ ipaclient_subid }}" subid: "{{ ipaclient_subid }}"
fixed_primary: "{{ ipasssd_fixed_primary }}" fixed_primary: "{{ ipassd_fixed_primary
permit: "{{ ipasssd_permit }}" | default(ipasssd_fixed_primary) }}"
no_krb5_offline_passwords: "{{ ipasssd_no_krb5_offline_passwords }}" permit: "{{ ipassd_permit | default(ipasssd_permit) }}"
no_krb5_offline_passwords:
"{{ ipassd_no_krb5_offline_passwords
| default(ipasssd_no_krb5_offline_passwords) }}"
no_dns_sshfp: "{{ ipaclient_no_dns_sshfp }}" no_dns_sshfp: "{{ ipaclient_no_dns_sshfp }}"
nosssd_files: "{{ result_ipaclient_test.nosssd_files }}" nosssd_files: "{{ result_ipaclient_test.nosssd_files }}"
selinux_works: "{{ result_ipaclient_test.selinux_works }}" selinux_works: "{{ result_ipaclient_test.selinux_works }}"

View File

@@ -1,7 +1,6 @@
--- ---
# vars/Debian.yml # vars/Debian.yml
ipaclient_packages: [ "freeipa-client" ] ipaclient_packages: [ "freeipa-client" ]
ipaclient_packages_dot: [ ]
# Debian Buster must use python2 as Python interpreter due # Debian Buster must use python2 as Python interpreter due
# to the way freeipa-client package is defined. # to the way freeipa-client package is defined.
# You must install package python2.7 before executing this role. # You must install package python2.7 before executing this role.

View File

@@ -2,4 +2,3 @@
# vars/Debian.yml # vars/Debian.yml
--- ---
ipaclient_packages: [ "freeipa-client" ] ipaclient_packages: [ "freeipa-client" ]
ipaclient_packages_dot: [ ]

View File

@@ -2,4 +2,3 @@
# vars/RedHat-7 # vars/RedHat-7
--- ---
ipaclient_packages: [ "ipa-client", "libselinux-python" ] ipaclient_packages: [ "ipa-client", "libselinux-python" ]
ipaclient_packages_dot: [ ]

View File

@@ -2,4 +2,3 @@
# vars/RedHat-8.yml # vars/RedHat-8.yml
--- ---
ipaclient_packages: [ "@idm:DL1/client" ] ipaclient_packages: [ "@idm:DL1/client" ]
ipaclient_packages_dot: [ ]

View File

@@ -1,7 +1,6 @@
# vars/Ubuntu-18.04.yml # vars/Ubuntu-18.04.yml
--- ---
ipaclient_packages: [ "freeipa-client" ] ipaclient_packages: [ "freeipa-client" ]
ipaclient_packages_dot: [ ]
# Ubuntu Bionic Beaver must use python2 as Python interpreter due # Ubuntu Bionic Beaver must use python2 as Python interpreter due
# to the way python-ipalib package is defined. # to the way python-ipalib package is defined.
# Package python2.7 must be installed before executing this role. # Package python2.7 must be installed before executing this role.

View File

@@ -2,4 +2,3 @@
# vars/default.yml # vars/default.yml
--- ---
ipaclient_packages: [ "ipa-client", "python3-libselinux" ] ipaclient_packages: [ "ipa-client", "python3-libselinux" ]
ipaclient_packages_dot: [ "ipa-client-encrypted-dns" ]

View File

@@ -270,11 +270,6 @@ Variable | Description | Required
`ipareplica_auto_forwarders` | Add DNS forwarders configured in /etc/resolv.conf to the list of forwarders used by IPA DNS. (bool, default: false) | no `ipareplica_auto_forwarders` | Add DNS forwarders configured in /etc/resolv.conf to the list of forwarders used by IPA DNS. (bool, default: false) | no
`ipareplica_forward_policy` | DNS forwarding policy for global forwarders specified using other options. (choice: first,only) | no `ipareplica_forward_policy` | DNS forwarding policy for global forwarders specified using other options. (choice: first,only) | no
`ipareplica_no_dnssec_validation` | Disable DNSSEC validation on this server. (bool, default: false) | no `ipareplica_no_dnssec_validation` | Disable DNSSEC validation on this server. (bool, default: false) | no
`ipareplica_dot_forwarders` | List of DNS over TLS forwarders. Required if `ipareplica_dns_over_tls` is enabled. (list of strings) | no
`ipareplica_dns_over_tls` \| `ipaclient_dns_over_tls` | Configure DNS over TLS. Requires FreeIPA version 4.12.5 or later. (bool, default: false) | no
`ipareplica_dns_over_tls_cert` | Certificate to use for DNS over TLS. If empty, a new certificate will be requested from IPA CA. (string) | no
`ipareplica_dns_over_tls_key` | Key for certificate specified in `ipareplica_dns_over_tls_cert`. (string) | no
`ipareplica_dns_policy` | Encrypted DNS policy. Only usable if `ipareplica_dns_over_tls` is enabled. (choice: relaxed, enforced, default: relaxed) | no
AD trust Variables AD trust Variables
------------------ ------------------

View File

@@ -224,32 +224,6 @@ options:
type: bool type: bool
default: no default: no
required: no required: no
dot_forwarders:
description: List of DNS over TLS forwarders
type: list
elements: str
default: []
required: no
dns_over_tls:
description: Configure DNS over TLS
type: bool
default: no
required: no
dns_over_tls_cert:
description:
Certificate to use for DNS over TLS. If empty, a new
certificate will be requested from IPA CA
type: str
required: no
dns_over_tls_key:
description: Key for certificate specified in dns_over_tls_cert
type: str
required: no
dns_policy:
description: Encrypted DNS policy
type: str
choices: ['relaxed', 'enforced']
default: 'relaxed'
enable_compat: enable_compat:
description: Enable support for trusted domains for old clients description: Enable support for trusted domains for old clients
type: bool type: bool
@@ -380,15 +354,6 @@ def main():
choices=['first', 'only'], default=None), choices=['first', 'only'], default=None),
no_dnssec_validation=dict(required=False, type='bool', no_dnssec_validation=dict(required=False, type='bool',
default=False), default=False),
dot_forwarders=dict(required=False, type='list', elements='str',
default=[]),
dns_over_tls=dict(required=False, type='bool',
default=False),
dns_over_tls_cert=dict(required=False, type='str'),
dns_over_tls_key=dict(required=False, type='str', no_log=True),
dns_policy=dict(required=False, type='str',
choices=['relaxed', 'enforced'],
default='relaxed'),
# ad trust # ad trust
enable_compat=dict(required=False, type='bool', default=False), enable_compat=dict(required=False, type='bool', default=False),
netbios_name=dict(required=False, type='str'), netbios_name=dict(required=False, type='str'),
@@ -465,11 +430,6 @@ def main():
options.forward_policy = ansible_module.params.get('forward_policy') options.forward_policy = ansible_module.params.get('forward_policy')
options.no_dnssec_validation = ansible_module.params.get( options.no_dnssec_validation = ansible_module.params.get(
'no_dnssec_validation') 'no_dnssec_validation')
options.dot_forwarders = ansible_module.params.get('dot_forwarders')
options.dns_over_tls = ansible_module.params.get('dns_over_tls')
options.dns_over_tls_cert = ansible_module.params.get('dns_over_tls_cert')
options.dns_over_tls_key = ansible_module.params.get('dns_over_tls_key')
options.dns_policy = ansible_module.params.get('dns_policy')
# ad trust # ad trust
options.enable_compat = ansible_module.params.get('enable_compat') options.enable_compat = ansible_module.params.get('enable_compat')
options.netbios_name = ansible_module.params.get('netbios_name') options.netbios_name = ansible_module.params.get('netbios_name')

View File

@@ -72,32 +72,6 @@ options:
type: bool type: bool
default: no default: no
required: no required: no
dot_forwarders:
description: List of DNS over TLS forwarders
type: list
elements: str
default: []
required: no
dns_over_tls:
description: Configure DNS over TLS
type: bool
default: no
required: no
dns_over_tls_cert:
description:
Certificate to use for DNS over TLS. If empty, a new
certificate will be requested from IPA CA
type: str
required: no
dns_over_tls_key:
description: Key for certificate specified in dns_over_tls_cert
type: str
required: no
dns_policy:
description: Encrypted DNS policy
type: str
choices: ['relaxed', 'enforced']
default: 'relaxed'
dns_ip_addresses: dns_ip_addresses:
description: The dns ip_addresses setting description: The dns ip_addresses setting
type: list type: list
@@ -143,9 +117,6 @@ from ansible.module_utils.ansible_ipa_replica import (
gen_ReplicaConfig, gen_remote_api, api, redirect_stdout, dns, gen_ReplicaConfig, gen_remote_api, api, redirect_stdout, dns,
ansible_module_get_parsed_ip_addresses ansible_module_get_parsed_ip_addresses
) )
# pylint: disable=unused-import
from ansible.module_utils.ansible_ipa_replica import bindinstance # noqa: F401
# pylint: enable=unused-import
def main(): def main():
@@ -164,14 +135,6 @@ def main():
choices=['first', 'only'], default=None), choices=['first', 'only'], default=None),
no_dnssec_validation=dict(required=False, type='bool', no_dnssec_validation=dict(required=False, type='bool',
default=False), default=False),
dot_forwarders=dict(required=False, type='list', elements='str',
default=[]),
dns_over_tls=dict(required=False, type='bool', default=False),
dns_over_tls_cert=dict(required=False, type='str'),
dns_over_tls_key=dict(required=False, type='str', no_log=True),
dns_policy=dict(required=False, type='str',
choices=['relaxed', 'enforced'],
default='relaxed'),
# additional # additional
dns_ip_addresses=dict(required=True, type='list', elements='str'), dns_ip_addresses=dict(required=True, type='list', elements='str'),
dns_reverse_zones=dict(required=True, type='list', elements='str'), dns_reverse_zones=dict(required=True, type='list', elements='str'),
@@ -204,11 +167,6 @@ def main():
options.forward_policy = ansible_module.params.get('forward_policy') options.forward_policy = ansible_module.params.get('forward_policy')
options.no_dnssec_validation = ansible_module.params.get( options.no_dnssec_validation = ansible_module.params.get(
'no_dnssec_validation') 'no_dnssec_validation')
options.dot_forwarders = ansible_module.params.get('dot_forwarders')
options.dns_over_tls = ansible_module.params.get('dns_over_tls')
options.dns_over_tls_cert = ansible_module.params.get('dns_over_tls_cert')
options.dns_over_tls_key = ansible_module.params.get('dns_over_tls_key')
options.dns_policy = ansible_module.params.get('dns_policy')
# additional # additional
dns.ip_addresses = ansible_module_get_parsed_ip_addresses( dns.ip_addresses = ansible_module_get_parsed_ip_addresses(
ansible_module, 'dns_ip_addresses') ansible_module, 'dns_ip_addresses')

View File

@@ -181,32 +181,6 @@ options:
type: bool type: bool
default: no default: no
required: no required: no
dot_forwarders:
description: List of DNS over TLS forwarders
type: list
elements: str
default: []
required: no
dns_over_tls:
description: Configure DNS over TLS
type: bool
default: no
required: no
dns_over_tls_cert:
description:
Certificate to use for DNS over TLS. If empty, a new
certificate will be requested from IPA CA
type: str
required: no
dns_over_tls_key:
description: Key for certificate specified in dns_over_tls_cert
type: str
required: no
dns_policy:
description: Encrypted DNS policy
type: str
choices: ['relaxed', 'enforced']
default: 'relaxed'
author: author:
- Thomas Woerner (@t-woerner) - Thomas Woerner (@t-woerner)
''' '''
@@ -225,8 +199,7 @@ from ansible.module_utils.ansible_ipa_replica import (
paths, sysrestore, ansible_module_get_parsed_ip_addresses, service, paths, sysrestore, ansible_module_get_parsed_ip_addresses, service,
redirect_stdout, create_ipa_conf, ipautil, redirect_stdout, create_ipa_conf, ipautil,
x509, validate_domain_name, common_check, x509, validate_domain_name, common_check,
IPA_PYTHON_VERSION, getargspec, adtrustinstance, install_ca_cert, IPA_PYTHON_VERSION, getargspec, adtrustinstance, install_ca_cert
services, CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION
) )
@@ -277,14 +250,6 @@ def main():
choices=['first', 'only'], default=None), choices=['first', 'only'], default=None),
no_dnssec_validation=dict(required=False, type='bool', no_dnssec_validation=dict(required=False, type='bool',
default=False), default=False),
dot_forwarders=dict(required=False, type='list', elements='str',
default=[]),
dns_over_tls=dict(required=False, type='bool', default=False),
dns_over_tls_cert=dict(required=False, type='str'),
dns_over_tls_key=dict(required=False, type='str', no_log=True),
dns_policy=dict(required=False, type='str',
choices=['relaxed', 'enforced'],
default='relaxed'),
), ),
) )
@@ -333,11 +298,6 @@ def main():
options.forward_policy = ansible_module.params.get('forward_policy') options.forward_policy = ansible_module.params.get('forward_policy')
options.no_dnssec_validation = ansible_module.params.get( options.no_dnssec_validation = ansible_module.params.get(
'no_dnssec_validation') 'no_dnssec_validation')
options.dot_forwarders = ansible_module.params.get('dot_forwarders')
options.dns_over_tls = ansible_module.params.get('dns_over_tls')
options.dns_over_tls_cert = ansible_module.params.get('dns_over_tls_cert')
options.dns_over_tls_key = ansible_module.params.get('dns_over_tls_key')
options.dns_policy = ansible_module.params.get('dns_policy')
########################################################################## ##########################################################################
# replica init ########################################################### # replica init ###########################################################
@@ -459,14 +419,6 @@ def main():
ansible_module.fail_json( ansible_module.fail_json(
msg="You cannot specify a --no-dnssec-validation option " msg="You cannot specify a --no-dnssec-validation option "
"without the --setup-dns option") "without the --setup-dns option")
if installer.dns_over_tls_cert:
ansible_module.fail_json(
msg="You cannot specify a --dns-over-tls-cert option "
"without the --setup-dns option")
if installer.dns_over_tls_key:
ansible_module.fail_json(
msg="You cannot specify a --dns-over-tls-key option "
"without the --setup-dns option")
elif installer.forwarders and installer.no_forwarders: elif installer.forwarders and installer.no_forwarders:
ansible_module.fail_json( ansible_module.fail_json(
msg="You cannot specify a --forwarder option together with " msg="You cannot specify a --forwarder option together with "
@@ -483,31 +435,6 @@ def main():
ansible_module.fail_json( ansible_module.fail_json(
msg="You cannot specify a --auto-reverse option together with " msg="You cannot specify a --auto-reverse option together with "
"--no-reverse") "--no-reverse")
elif installer.dot_forwarders and not installer.dns_over_tls:
ansible_module.fail_json(
msg="You cannot specify a --dot-forwarder option "
"without the --dns-over-tls option")
elif (installer.dns_over_tls
and not services.knownservices["unbound"].is_installed()):
ansible_module.fail_json(
msg="To enable DNS over TLS, package ipa-server-encrypted-dns "
"must be installed.")
elif installer.dns_policy == "enforced" and not installer.dns_over_tls:
ansible_module.fail_json(
msg="You cannot specify a --dns-policy option "
"without the --dns-over-tls option")
elif installer.dns_over_tls_cert and not installer.dns_over_tls:
ansible_module.fail_json(
msg="You cannot specify a --dns-over-tls-cert option "
"without the --dns-over-tls option")
elif installer.dns_over_tls_key and not installer.dns_over_tls:
ansible_module.fail_json(
msg="You cannot specify a --dns-over-tls-key option "
"without the --dns-over-tls option")
elif bool(installer.dns_over_tls_key) != bool(installer.dns_over_tls_cert):
ansible_module.fail_json(
msg="You cannot specify a --dns-over-tls-key option "
"without the --dns-over-tls-cert option and vice versa")
# replica installers # replica installers
if installer.servers and not installer.domain_name: if installer.servers and not installer.domain_name:
@@ -522,10 +449,6 @@ def main():
ansible_module.fail_json( ansible_module.fail_json(
msg="You must specify at least one of --forwarder, " msg="You must specify at least one of --forwarder, "
"--auto-forwarders, or --no-forwarders options") "--auto-forwarders, or --no-forwarders options")
if installer.dns_over_tls and not installer.dot_forwarders:
ansible_module.fail_json(
msg="You must specify --dot-forwarder "
"when enabling DNS over TLS")
if installer.dirsrv_config_file is not None and \ if installer.dirsrv_config_file is not None and \
not os.path.exists(installer.dirsrv_config_file): not os.path.exists(installer.dirsrv_config_file):
@@ -563,11 +486,6 @@ def main():
if installer.domain_name is not None: if installer.domain_name is not None:
validate_domain_name(installer.domain_name) validate_domain_name(installer.domain_name)
if installer.dns_over_tls and not CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION:
ansible_module.fail_json(
msg="Important patches for DNS over TLS are missing in your "
"IPA version.")
########################################################################## ##########################################################################
# replica promote_check excerpts ######################################### # replica promote_check excerpts #########################################
########################################################################## ##########################################################################

View File

@@ -23,9 +23,7 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
# pylint: disable=invalid-name
__metaclass__ = type __metaclass__ = type
# pylint: enable=invalid-name
__all__ = ["contextlib", "dnsexception", "dnsresolver", "dnsreversename", __all__ = ["contextlib", "dnsexception", "dnsresolver", "dnsreversename",
"parse_version", "IPAChangeConf", "parse_version", "IPAChangeConf",
@@ -51,8 +49,7 @@ __all__ = ["contextlib", "dnsexception", "dnsresolver", "dnsreversename",
"dnsname", "kernel_keyring", "krbinstance", "getargspec", "dnsname", "kernel_keyring", "krbinstance", "getargspec",
"adtrustinstance", "paths", "api", "dsinstance", "ipaldap", "Env", "adtrustinstance", "paths", "api", "dsinstance", "ipaldap", "Env",
"ipautil", "installutils", "IPA_PYTHON_VERSION", "NUM_VERSION", "ipautil", "installutils", "IPA_PYTHON_VERSION", "NUM_VERSION",
"ReplicaConfig", "create_api", "clean_up_hsm_nicknames", "ReplicaConfig", "create_api", "clean_up_hsm_nicknames"]
"CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION"]
import sys import sys
import logging import logging
@@ -80,17 +77,9 @@ except ImportError:
return ArgSpec(args, varargs, varkw, defaults) return ArgSpec(args, varargs, varkw, defaults)
ANSIBLE_IPA_REPLICA_MODULE_IMPORT_ERROR = None # pylint: disable=invalid-name
try: try:
from contextlib import contextmanager as contextlib_contextmanager from contextlib import contextmanager as contextlib_contextmanager
from ipapython.version import NUM_VERSION, VERSION from ipapython.version import NUM_VERSION, VERSION
try:
from ipapython.version import parse_version
except ImportError:
# In IPA we either need pkg_resources or packaging Version
# class to compare versions with check_remote_version, so
# we let an exception to be raised if neither is available.
from pkg_resources import parse_version
if NUM_VERSION < 30201: if NUM_VERSION < 30201:
# See ipapython/version.py # See ipapython/version.py
@@ -110,6 +99,8 @@ try:
import dns.resolver as dnsresolver import dns.resolver as dnsresolver
import dns.reversename as dnsreversename import dns.reversename as dnsreversename
from pkg_resources import parse_version
from ipaclient.install.ipachangeconf import IPAChangeConf from ipaclient.install.ipachangeconf import IPAChangeConf
from ipalib.install import certstore, sysrestore from ipalib.install import certstore, sysrestore
from ipapython.ipautil import ipa_generate_password from ipapython.ipautil import ipa_generate_password
@@ -191,30 +182,19 @@ try:
from ipaserver.install import ntpinstance from ipaserver.install import ntpinstance
time_service = "ntpd" # pylint: disable=invalid-name time_service = "ntpd" # pylint: disable=invalid-name
try:
# pylint: disable=invalid-name
CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION = False
# pylint: enable=invalid-name
from ipaclient.install.client import ClientInstallInterface
except ImportError:
pass
else:
if hasattr(ClientInstallInterface, "no_dnssec_validation"):
# pylint: disable=invalid-name
CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION = True
# pylint: enable=invalid-name
else: else:
# IPA version < 4.6 # IPA version < 4.6
raise RuntimeError("freeipa version '%s' is too old" % VERSION) raise RuntimeError("freeipa version '%s' is too old" % VERSION)
except ImportError as _err: except ImportError as _err:
# pylint: disable=invalid-name
ANSIBLE_IPA_REPLICA_MODULE_IMPORT_ERROR = str(_err) ANSIBLE_IPA_REPLICA_MODULE_IMPORT_ERROR = str(_err)
# pylint: enable=invalid-name
for attr in __all__: for attr in __all__:
setattr(sys.modules[__name__], attr, None) setattr(sys.modules[__name__], attr, None)
else:
ANSIBLE_IPA_REPLICA_MODULE_IMPORT_ERROR = None
logger = logging.getLogger("ipa-server-install") logger = logging.getLogger("ipa-server-install")
@@ -228,13 +208,11 @@ def setup_logging():
@contextlib_contextmanager @contextlib_contextmanager
def redirect_stdout(stream): def redirect_stdout(stream):
old_stdout = sys.stdout
sys.stdout = stream sys.stdout = stream
try: try:
yield stream yield stream
finally: finally:
sys.stdout = old_stdout sys.stdout = sys.__stdout__
class AnsibleModuleLog(): class AnsibleModuleLog():
@@ -353,7 +331,6 @@ options.add_agents = False
# ServerReplicaInstall # ServerReplicaInstall
options.subject_base = None options.subject_base = None
options.ca_subject = None options.ca_subject = None
# pylint: enable=attribute-defined-outside-init # pylint: enable=attribute-defined-outside-init

View File

@@ -1,42 +1,32 @@
--- ---
# tasks file for ipareplica # tasks file for ipareplica
- name: Install - Set ipareplica__dns_over_lts - name: Package installation
ansible.builtin.set_fact:
ipareplica__dns_over_tls: "{{ ipareplica_dns_over_tls | default(ipaclient_dns_over_tls) | default(False) }}"
- name: Install - Package installation
when: ipareplica_install_packages | bool when: ipareplica_install_packages | bool
block: block:
- name: Install - Set packages for installation - name: Install - Ensure IPA replica packages are installed
ansible.builtin.set_fact: ansible.builtin.package:
_ipapackages: "{{ ipareplica_packages }}" name: "{{ ipareplica_packages }}"
state: present
- name: Install - Set packages for installlation, add DNS - name: Install - Ensure IPA replica packages for dns are installed
ansible.builtin.set_fact: ansible.builtin.package:
_ipapackages: "{{ _ipapackages + ipareplica_packages_dns }}" name: "{{ ipareplica_packages_dns }}"
state: present
when: ipareplica_setup_dns | bool when: ipareplica_setup_dns | bool
- name: Install - Set packages for installlation, add DOT - name: Install - Ensure IPA replica packages for adtrust are installed
ansible.builtin.set_fact: ansible.builtin.package:
_ipapackages: "{{ _ipapackages + ipareplica_packages_dot }}" name: "{{ ipareplica_packages_adtrust }}"
when: ipareplica__dns_over_tls | bool state: present
- name: Install - Set packages for installlation, add adtrust
ansible.builtin.set_fact:
_ipapackages: "{{ _ipapackages + ipareplica_packages_adtrust }}"
when: ipareplica_setup_adtrust | bool when: ipareplica_setup_adtrust | bool
- name: Install - Set packages for installlation, add firewalld - name: Install - Ensure that firewall packages installed
ansible.builtin.set_fact:
_ipapackages: "{{ _ipapackages + ipareplica_packages_firewalld }}"
when: ipareplica_setup_firewalld | bool
- name: Install - Ensure that packages are installed
ansible.builtin.package: ansible.builtin.package:
name: "{{ _ipapackages }}" name: "{{ ipareplica_packages_firewalld }}"
state: present state: present
when: ipareplica_setup_firewalld | bool
- name: Firewall configuration - name: Firewall configuration
when: ipareplica_setup_firewalld | bool when: ipareplica_setup_firewalld | bool
@@ -47,21 +37,20 @@
enabled: yes enabled: yes
state: started state: started
- name: Firewalld - Verify zones - name: Firewalld - Verify runtime zone "{{ ipareplica_firewalld_zone }}"
ansible.builtin.shell: >
firewall-cmd
--info-zone="{{ ipareplica_firewalld_zone }}"
>/dev/null
when: ipareplica_firewalld_zone is defined when: ipareplica_firewalld_zone is defined
block:
- name: Firewalld - Verify runtime zone from ipareplica_firewalld_zone
ansible.builtin.shell: >
firewall-cmd
--info-zone="{{ ipareplica_firewalld_zone }}"
>/dev/null
- name: Firewalld - Verify permanent zone from ipareplica_firewalld_zone - name: Firewalld - Verify permanent zone "{{ ipareplica_firewalld_zone }}"
ansible.builtin.shell: > ansible.builtin.shell: >
firewall-cmd firewall-cmd
--permanent --permanent
--info-zone="{{ ipareplica_firewalld_zone }}" --info-zone="{{ ipareplica_firewalld_zone }}"
>/dev/null >/dev/null
when: ipareplica_firewalld_zone is defined
- name: Install - Set ipareplica_servers - name: Install - Set ipareplica_servers
ansible.builtin.set_fact: ansible.builtin.set_fact:
@@ -115,11 +104,6 @@
auto_forwarders: "{{ ipareplica_auto_forwarders }}" auto_forwarders: "{{ ipareplica_auto_forwarders }}"
forward_policy: "{{ ipareplica_forward_policy | default(omit) }}" forward_policy: "{{ ipareplica_forward_policy | default(omit) }}"
no_dnssec_validation: "{{ ipareplica_no_dnssec_validation }}" no_dnssec_validation: "{{ ipareplica_no_dnssec_validation }}"
dot_forwarders: "{{ ipareplica_dot_forwarders | default([]) }}"
dns_over_tls: "{{ ipareplica__dns_over_tls }}"
dns_over_tls_cert: "{{ ipareplica_dns_over_tls_cert | default(omit) }}"
dns_over_tls_key: "{{ ipareplica_dns_over_tls_key | default(omit) }}"
dns_policy: "{{ ipareplica_dns_policy | default(omit) }}"
register: result_ipareplica_test register: result_ipareplica_test
- name: Install - Deploy replica - name: Install - Deploy replica
@@ -143,8 +127,6 @@
ipaclient_hostname: "{{ result_ipareplica_test.hostname }}" ipaclient_hostname: "{{ result_ipareplica_test.hostname }}"
ipaclient_ip_addresses: "{{ ipareplica_ip_addresses | default(omit) }}" ipaclient_ip_addresses: "{{ ipareplica_ip_addresses | default(omit) }}"
ipaclient_install_packages: "{{ ipareplica_install_packages }}" ipaclient_install_packages: "{{ ipareplica_install_packages }}"
ipaclient_dns_over_tls: "{{ ipareplica__dns_over_tls }}"
ipaclient_no_dnssec_validation: "{{ ipareplica_no_dnssec_validation }}"
when: not result_ipareplica_test.client_enrolled when: not result_ipareplica_test.client_enrolled
- name: Install - Configure firewalld - name: Install - Configure firewalld
@@ -158,8 +140,6 @@
{{ "--add-service=freeipa-trust" if result_ipareplica_test.setup_adtrust {{ "--add-service=freeipa-trust" if result_ipareplica_test.setup_adtrust
else "" }} else "" }}
{{ "--add-service=dns" if ipareplica_setup_dns | bool else "" }} {{ "--add-service=dns" if ipareplica_setup_dns | bool else "" }}
{{ "--add-service=dns-over-tls" if ipareplica__dns_over_tls | bool
else "" }}
{{ "--add-service=ntp" if not ipaclient_no_ntp | bool else "" }} {{ "--add-service=ntp" if not ipaclient_no_ntp | bool else "" }}
when: ipareplica_setup_firewalld | bool when: ipareplica_setup_firewalld | bool
@@ -173,8 +153,6 @@
{{ "--add-service=freeipa-trust" if result_ipareplica_test.setup_adtrust {{ "--add-service=freeipa-trust" if result_ipareplica_test.setup_adtrust
else "" }} else "" }}
{{ "--add-service=dns" if ipareplica_setup_dns | bool else "" }} {{ "--add-service=dns" if ipareplica_setup_dns | bool else "" }}
{{ "--add-service=dns-over-tls" if ipareplica__dns_over_tls | bool
else "" }}
{{ "--add-service=ntp" if not ipaclient_no_ntp | bool else "" }} {{ "--add-service=ntp" if not ipaclient_no_ntp | bool else "" }}
when: ipareplica_setup_firewalld | bool when: ipareplica_setup_firewalld | bool
@@ -223,11 +201,6 @@
auto_forwarders: "{{ ipareplica_auto_forwarders }}" auto_forwarders: "{{ ipareplica_auto_forwarders }}"
forward_policy: "{{ ipareplica_forward_policy | default(omit) }}" forward_policy: "{{ ipareplica_forward_policy | default(omit) }}"
no_dnssec_validation: "{{ ipareplica_no_dnssec_validation }}" no_dnssec_validation: "{{ ipareplica_no_dnssec_validation }}"
dot_forwarders: "{{ ipareplica_dot_forwarders | default([]) }}"
dns_over_tls: "{{ ipareplica__dns_over_tls }}"
dns_over_tls_cert: "{{ ipareplica_dns_over_tls_cert | default(omit) }}"
dns_over_tls_key: "{{ ipareplica_dns_over_tls_key | default(omit) }}"
dns_policy: "{{ ipareplica_dns_policy | default(omit) }}"
### ad trust ### ### ad trust ###
enable_compat: "{{ ipareplica_enable_compat }}" enable_compat: "{{ ipareplica_enable_compat }}"
netbios_name: "{{ ipareplica_netbios_name | default(omit) }}" netbios_name: "{{ ipareplica_netbios_name | default(omit) }}"
@@ -744,11 +717,6 @@
result_ipareplica_prepare.forward_policy is result_ipareplica_prepare.forward_policy is
not none else omit }}" not none else omit }}"
no_dnssec_validation: "{{ ipareplica_no_dnssec_validation }}" no_dnssec_validation: "{{ ipareplica_no_dnssec_validation }}"
dot_forwarders: "{{ ipareplica_dot_forwarders | default([]) }}"
dns_over_tls: "{{ ipareplica__dns_over_tls }}"
dns_over_tls_cert: "{{ ipareplica_dns_over_tls_cert | default(omit) }}"
dns_over_tls_key: "{{ ipareplica_dns_over_tls_key | default(omit) }}"
dns_policy: "{{ ipareplica_dns_policy | default(omit) }}"
### additional ### ### additional ###
dns_ip_addresses: "{{ result_ipareplica_prepare.dns_ip_addresses }}" dns_ip_addresses: "{{ result_ipareplica_prepare.dns_ip_addresses }}"
dns_reverse_zones: "{{ result_ipareplica_prepare.dns_reverse_zones }}" dns_reverse_zones: "{{ result_ipareplica_prepare.dns_reverse_zones }}"

View File

@@ -3,6 +3,5 @@
--- ---
ipareplica_packages: [ "freeipa-server", "python3-libselinux" ] ipareplica_packages: [ "freeipa-server", "python3-libselinux" ]
ipareplica_packages_dns: [ "freeipa-server-dns" ] ipareplica_packages_dns: [ "freeipa-server-dns" ]
ipareplica_packages_dot: [ "freeipa-server-encrypted-dns" ]
ipareplica_packages_adtrust: [ "freeipa-server-trust-ad" ] ipareplica_packages_adtrust: [ "freeipa-server-trust-ad" ]
ipareplica_packages_firewalld: [ "firewalld" ] ipareplica_packages_firewalld: [ "firewalld" ]

View File

@@ -3,6 +3,5 @@
--- ---
ipareplica_packages: [ "ipa-server", "libselinux-python" ] ipareplica_packages: [ "ipa-server", "libselinux-python" ]
ipareplica_packages_dns: [ "ipa-server-dns" ] ipareplica_packages_dns: [ "ipa-server-dns" ]
ipareplica_packages_dot: [ ]
ipareplica_packages_adtrust: [ "ipa-server-trust-ad" ] ipareplica_packages_adtrust: [ "ipa-server-trust-ad" ]
ipareplica_packages_firewalld: [ "firewalld" ] ipareplica_packages_firewalld: [ "firewalld" ]

View File

@@ -3,6 +3,5 @@
--- ---
ipareplica_packages: [ "@idm:DL1/server" ] ipareplica_packages: [ "@idm:DL1/server" ]
ipareplica_packages_dns: [ "@idm:DL1/dns" ] ipareplica_packages_dns: [ "@idm:DL1/dns" ]
ipareplica_packages_dot: [ ]
ipareplica_packages_adtrust: [ "@idm:DL1/adtrust" ] ipareplica_packages_adtrust: [ "@idm:DL1/adtrust" ]
ipareplica_packages_firewalld: [ "firewalld" ] ipareplica_packages_firewalld: [ "firewalld" ]

View File

@@ -2,7 +2,6 @@
--- ---
ipareplica_packages: [ "freeipa-server" ] ipareplica_packages: [ "freeipa-server" ]
ipareplica_packages_dns: [ "freeipa-server-dns" ] ipareplica_packages_dns: [ "freeipa-server-dns" ]
ipareplica_packages_dot: [ ]
ipareplica_packages_adtrust: [ "freeipa-server-trust-ad" ] ipareplica_packages_adtrust: [ "freeipa-server-trust-ad" ]
ipareplica_packages_firewalld: [ "firewalld" ] ipareplica_packages_firewalld: [ "firewalld" ]
# Ubuntu Bionic Beaver must use python2 as Python interpreter due # Ubuntu Bionic Beaver must use python2 as Python interpreter due

View File

@@ -3,6 +3,5 @@
--- ---
ipareplica_packages: [ "freeipa-server" ] ipareplica_packages: [ "freeipa-server" ]
ipareplica_packages_dns: [ "freeipa-server-dns" ] ipareplica_packages_dns: [ "freeipa-server-dns" ]
ipareplica_packages_dot: [ ]
ipareplica_packages_adtrust: [ "freeipa-server-trust-ad" ] ipareplica_packages_adtrust: [ "freeipa-server-trust-ad" ]
ipareplica_packages_firewalld: [ "firewalld" ] ipareplica_packages_firewalld: [ "firewalld" ]

View File

@@ -1,8 +1,7 @@
# defaults file for ipareplica # defaults file for ipareplica
# vars/default.yml # vars/default.yml
--- ---
ipareplica_packages: [ "ipa-server", "python3-libselinux" ] ipareplica_packages: [ "freeipa-server", "python3-libselinux" ]
ipareplica_packages_dns: [ "ipa-server-dns" ] ipareplica_packages_dns: [ "freeipa-server-dns" ]
ipareplica_packages_dot: [ "ipa-server-encrypted-dns" ] ipareplica_packages_adtrust: [ "freeipa-server-trust-ad" ]
ipareplica_packages_adtrust: [ "ipa-server-trust-ad" ]
ipareplica_packages_firewalld: [ "firewalld" ] ipareplica_packages_firewalld: [ "firewalld" ]

View File

@@ -343,12 +343,6 @@ Variable | Description | Required
`ipaserver_auto_forwarders` | Add DNS forwarders configured in /etc/resolv.conf to the list of forwarders used by IPA DNS. (bool, default: false) | no `ipaserver_auto_forwarders` | Add DNS forwarders configured in /etc/resolv.conf to the list of forwarders used by IPA DNS. (bool, default: false) | no
`ipaserver_forward_policy` | DNS forwarding policy for global forwarders specified using other options. (choice: first, only) | no `ipaserver_forward_policy` | DNS forwarding policy for global forwarders specified using other options. (choice: first, only) | no
`ipaserver_no_dnssec_validation` | Disable DNSSEC validation on this server. (bool, default: false) | no `ipaserver_no_dnssec_validation` | Disable DNSSEC validation on this server. (bool, default: false) | no
`ipaserver_dot_forwarders` | List of DNS over TLS forwarders. Required if `ipaserver_dns_over_tls` is enabled. (list of strings) | no
`ipaserver_dns_over_tls` \| `ipaclient_dns_over_tls` | Configure DNS over TLS. Requires FreeIPA version 4.12.5 or later. (bool, default: false) | no
`ipaserver_dns_over_tls_cert` | Certificate to use for DNS over TLS. If empty, a new certificate will be requested from IPA CA. (string) | no
`ipaserver_dns_over_tls_key` | Key for certificate specified in `ipaserver_dns_over_tls_cert`. (string) | no
`ipaserver_dns_policy` | Encrypted DNS policy. Only usable if `ipaserver_dns_over_tls` is enabled. (choice: relaxed, enforced, default: relaxed) | no
AD trust Variables AD trust Variables
------------------ ------------------

View File

@@ -174,32 +174,6 @@ options:
type: bool type: bool
default: no default: no
required: no required: no
dot_forwarders:
description: List of DNS over TLS forwarders
type: list
elements: str
default: []
required: no
dns_over_tls:
description: Configure DNS over TLS
type: bool
default: no
required: no
dns_over_tls_cert:
description:
Certificate to use for DNS over TLS. If empty, a new
certificate will be requested from IPA CA
type: str
required: no
dns_over_tls_key:
description: Key for certificate specified in dns_over_tls_cert
type: str
required: no
dns_policy:
description: Encrypted DNS policy
type: str
choices: ['relaxed', 'enforced']
default: 'relaxed'
enable_compat: enable_compat:
description: Enable support for trusted domains for old clients description: Enable support for trusted domains for old clients
type: bool type: bool
@@ -306,15 +280,6 @@ def main():
choices=['first', 'only'], default=None), choices=['first', 'only'], default=None),
no_dnssec_validation=dict(required=False, type='bool', no_dnssec_validation=dict(required=False, type='bool',
default=False), default=False),
dot_forwarders=dict(required=False, type='list', elements='str',
default=[]),
dns_over_tls=dict(required=False, type='bool',
default=False),
dns_over_tls_cert=dict(required=False, type='str'),
dns_over_tls_key=dict(required=False, type='str', no_log=True),
dns_policy=dict(required=False, type='str',
choices=['relaxed', 'enforced'],
default='relaxed'),
# ad trust # ad trust
enable_compat=dict(required=False, type='bool', default=False), enable_compat=dict(required=False, type='bool', default=False),
netbios_name=dict(required=False, type='str'), netbios_name=dict(required=False, type='str'),
@@ -395,11 +360,6 @@ def main():
options.forward_policy = ansible_module.params.get('forward_policy') options.forward_policy = ansible_module.params.get('forward_policy')
options.no_dnssec_validation = ansible_module.params.get( options.no_dnssec_validation = ansible_module.params.get(
'no_dnssec_validation') 'no_dnssec_validation')
options.dot_forwarders = ansible_module.params.get('dot_forwarders')
options.dns_over_tls = ansible_module.params.get('dns_over_tls')
options.dns_over_tls_cert = ansible_module.params.get('dns_over_tls_cert')
options.dns_over_tls_key = ansible_module.params.get('dns_over_tls_key')
options.dns_policy = ansible_module.params.get('dns_policy')
# ad trust # ad trust
options.enable_compat = ansible_module.params.get('enable_compat') options.enable_compat = ansible_module.params.get('enable_compat')
options.netbios_name = ansible_module.params.get('netbios_name') options.netbios_name = ansible_module.params.get('netbios_name')

View File

@@ -83,32 +83,6 @@ options:
type: bool type: bool
default: no default: no
required: no required: no
dot_forwarders:
description: List of DNS over TLS forwarders
type: list
elements: str
default: []
required: no
dns_over_tls:
description: Configure DNS over TLS
type: bool
default: no
required: no
dns_over_tls_cert:
description:
Certificate to use for DNS over TLS. If empty, a new
certificate will be requested from IPA CA
type: str
required: no
dns_over_tls_key:
description: Key for certificate specified in dns_over_tls_cert
type: str
required: no
dns_policy:
description: Encrypted DNS policy
type: str
choices: ['relaxed', 'enforced']
default: 'relaxed'
dns_ip_addresses: dns_ip_addresses:
description: The dns ip_addresses setting description: The dns ip_addresses setting
type: list type: list
@@ -133,13 +107,9 @@ from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_server import ( from ansible.module_utils.ansible_ipa_server import (
check_imports, AnsibleModuleLog, setup_logging, options, paths, dns, check_imports, AnsibleModuleLog, setup_logging, options, paths, dns,
ansible_module_get_parsed_ip_addresses, sysrestore, api_Backend_ldap2, ansible_module_get_parsed_ip_addresses, sysrestore, api_Backend_ldap2,
redirect_stdout redirect_stdout, bindinstance
) )
# pylint: disable=unused-import
from ansible.module_utils.ansible_ipa_server import bindinstance # noqa: F401
# pylint: enable=unused-import
def main(): def main():
ansible_module = AnsibleModule( ansible_module = AnsibleModule(
@@ -160,14 +130,6 @@ def main():
default='first'), default='first'),
no_dnssec_validation=dict(required=False, type='bool', no_dnssec_validation=dict(required=False, type='bool',
default=False), default=False),
dot_forwarders=dict(required=False, type='list', elements='str',
default=[]),
dns_over_tls=dict(required=False, type='bool', default=False),
dns_over_tls_cert=dict(required=False, type='str'),
dns_over_tls_key=dict(required=False, type='str', no_log=True),
dns_policy=dict(required=False, type='str',
choices=['relaxed', 'enforced'],
default='relaxed'),
# additional # additional
dns_ip_addresses=dict(required=True, type='list', elements='str'), dns_ip_addresses=dict(required=True, type='list', elements='str'),
dns_reverse_zones=dict(required=True, type='list', elements='str'), dns_reverse_zones=dict(required=True, type='list', elements='str'),
@@ -196,11 +158,6 @@ def main():
options.forward_policy = ansible_module.params.get('forward_policy') options.forward_policy = ansible_module.params.get('forward_policy')
options.no_dnssec_validation = ansible_module.params.get( options.no_dnssec_validation = ansible_module.params.get(
'no_dnssec_validation') 'no_dnssec_validation')
options.dot_forwarders = ansible_module.params.get('dot_forwarders')
options.dns_over_tls = ansible_module.params.get('dns_over_tls')
options.dns_over_tls_cert = ansible_module.params.get('dns_over_tls_cert')
options.dns_over_tls_key = ansible_module.params.get('dns_over_tls_key')
options.dns_policy = ansible_module.params.get('dns_policy')
# additional # additional
dns.ip_addresses = ansible_module_get_parsed_ip_addresses( dns.ip_addresses = ansible_module_get_parsed_ip_addresses(
ansible_module, 'dns_ip_addresses') ansible_module, 'dns_ip_addresses')
@@ -208,16 +165,25 @@ def main():
# init ################################################################## # init ##################################################################
# pylint: disable=unused-variable fstore = sysrestore.FileStore(paths.SYSRESTORE)
fstore = sysrestore.FileStore(paths.SYSRESTORE) # noqa: F841
# pylint: enable=unused-variable
api_Backend_ldap2(options.host_name, options.setup_ca, connect=True) api_Backend_ldap2(options.host_name, options.setup_ca, connect=True)
# setup dns ############################################################# # setup dns #############################################################
with redirect_stdout(ansible_log): with redirect_stdout(ansible_log):
dns.install(False, False, options) if options.setup_dns:
dns.install(False, False, options)
else:
# Create a BIND instance
bind = bindinstance.BindInstance(fstore)
bind.set_output(ansible_log)
bind.setup(options.host_name, options.ip_addresses,
options.realm_name,
options.domain_name, (), 'first', (),
zonemgr=options.zonemgr,
no_dnssec_validation=options.no_dnssec_validation)
bind.create_file_with_system_records()
# done ################################################################## # done ##################################################################

View File

@@ -265,32 +265,6 @@ options:
type: bool type: bool
default: no default: no
required: no required: no
dot_forwarders:
description: List of DNS over TLS forwarders
type: list
elements: str
default: []
required: no
dns_over_tls:
description: Configure DNS over TLS
type: bool
default: no
required: no
dns_over_tls_cert:
description:
Certificate to use for DNS over TLS. If empty, a new
certificate will be requested from IPA CA
type: str
required: no
dns_over_tls_key:
description: Key for certificate specified in dns_over_tls_cert
type: str
required: no
dns_policy:
description: Encrypted DNS policy
type: str
choices: ['relaxed', 'enforced']
default: 'relaxed'
enable_compat: enable_compat:
description: Enable support for trusted domains for old clients description: Enable support for trusted domains for old clients
type: bool type: bool
@@ -338,8 +312,7 @@ from ansible.module_utils.ansible_ipa_server import (
check_dirsrv, ScriptError, get_fqdn, verify_fqdn, BadHostError, check_dirsrv, ScriptError, get_fqdn, verify_fqdn, BadHostError,
validate_domain_name, load_pkcs12, IPA_PYTHON_VERSION, validate_domain_name, load_pkcs12, IPA_PYTHON_VERSION,
encode_certificate, check_available_memory, getargspec, adtrustinstance, encode_certificate, check_available_memory, getargspec, adtrustinstance,
get_min_idstart, SerialNumber, services, service, get_min_idstart, SerialNumber
CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION
) )
from ansible.module_utils import six from ansible.module_utils import six
@@ -423,14 +396,6 @@ def main():
choices=['first', 'only'], default=None), choices=['first', 'only'], default=None),
no_dnssec_validation=dict(required=False, type='bool', no_dnssec_validation=dict(required=False, type='bool',
default=False), default=False),
dot_forwarders=dict(required=False, type='list', elements='str',
default=[]),
dns_over_tls=dict(required=False, type='bool', default=False),
dns_over_tls_cert=dict(required=False, type='str'),
dns_over_tls_key=dict(required=False, type='str', no_log=True),
dns_policy=dict(required=False, type='str',
choices=['relaxed', 'enforced'],
default='relaxed'),
# ad trust # ad trust
enable_compat=dict(required=False, type='bool', default=False), enable_compat=dict(required=False, type='bool', default=False),
netbios_name=dict(required=False, type='str'), netbios_name=dict(required=False, type='str'),
@@ -517,11 +482,6 @@ def main():
options.forward_policy = ansible_module.params.get('forward_policy') options.forward_policy = ansible_module.params.get('forward_policy')
options.no_dnssec_validation = ansible_module.params.get( options.no_dnssec_validation = ansible_module.params.get(
'no_dnssec_validation') 'no_dnssec_validation')
options.dot_forwarders = ansible_module.params.get('dot_forwarders')
options.dns_over_tls = ansible_module.params.get('dns_over_tls')
options.dns_over_tls_cert = ansible_module.params.get('dns_over_tls_cert')
options.dns_over_tls_key = ansible_module.params.get('dns_over_tls_key')
options.dns_policy = ansible_module.params.get('dns_policy')
# ad trust # ad trust
options.enable_compat = ansible_module.params.get('enable_compat') options.enable_compat = ansible_module.params.get('enable_compat')
options.netbios_name = ansible_module.params.get('netbios_name') options.netbios_name = ansible_module.params.get('netbios_name')
@@ -643,14 +603,6 @@ def main():
raise RuntimeError( raise RuntimeError(
"You cannot specify a --no-dnssec-validation option " "You cannot specify a --no-dnssec-validation option "
"without the --setup-dns option") "without the --setup-dns option")
if self.dns_over_tls_cert:
raise RuntimeError(
"You cannot specify a --dns-over-tls-cert option "
"without the --setup-dns option")
if self.dns_over_tls_key:
raise RuntimeError(
"You cannot specify a --dns-over-tls-key option "
"without the --setup-dns option")
elif self.forwarders and self.no_forwarders: elif self.forwarders and self.no_forwarders:
raise RuntimeError( raise RuntimeError(
"You cannot specify a --forwarder option together with " "You cannot specify a --forwarder option together with "
@@ -667,31 +619,7 @@ def main():
raise RuntimeError( raise RuntimeError(
"You cannot specify a --auto-reverse option together with " "You cannot specify a --auto-reverse option together with "
"--no-reverse") "--no-reverse")
elif self.dot_forwarders and not self.dns_over_tls:
raise RuntimeError(
"You cannot specify a --dot-forwarder option "
"without the --dns-over-tls option")
elif (self.dns_over_tls
and not services.knownservices["unbound"].is_installed()):
raise RuntimeError(
"To enable DNS over TLS, package ipa-server-encrypted-dns "
"must be installed.")
elif self.dns_policy == "enforced" and not self.dns_over_tls:
raise RuntimeError(
"You cannot specify a --dns-policy option "
"without the --dns-over-tls option")
elif self.dns_over_tls_cert and not self.dns_over_tls:
raise RuntimeError(
"You cannot specify a --dns-over-tls-cert option "
"without the --dns-over-tls option")
elif self.dns_over_tls_key and not self.dns_over_tls:
raise RuntimeError(
"You cannot specify a --dns-over-tls-key option "
"without the --dns-over-tls option")
elif bool(self.dns_over_tls_key) != bool(self.dns_over_tls_cert):
raise RuntimeError(
"You cannot specify a --dns-over-tls-key option "
"without the --dns-over-tls-cert option and vice versa")
if not self.setup_adtrust: if not self.setup_adtrust:
if self.add_agents: if self.add_agents:
raise RuntimeError( raise RuntimeError(
@@ -749,10 +677,6 @@ def main():
raise RuntimeError( raise RuntimeError(
"You must specify at least one of --forwarder, " "You must specify at least one of --forwarder, "
"--auto-forwarders, or --no-forwarders options") "--auto-forwarders, or --no-forwarders options")
if self.dns_over_tls and not self.dot_forwarders:
raise RuntimeError(
"You must specify --dot-forwarder "
"when enabling DNS over TLS")
any_ignore_option_true = any( any_ignore_option_true = any(
[self.ignore_topology_disconnect, self.ignore_last_of_role]) [self.ignore_topology_disconnect, self.ignore_last_of_role])
@@ -795,19 +719,6 @@ def main():
# ####################################################################### # #######################################################################
if options.dns_over_tls and not CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION:
ansible_module.fail_json(
msg="Important patches for DNS over TLS are missing in your "
"IPA version.")
client_dns_over_tls = self.dns_over_tls
if self.dns_over_tls and not self.setup_dns:
service.print_msg("Warning: --dns-over-tls option "
"specified without --setup-dns, ignoring")
client_dns_over_tls = False
# #######################################################################
# If any of the key file options are selected, all are required. # If any of the key file options are selected, all are required.
cert_file_req = (options.dirsrv_cert_files, options.http_cert_files) cert_file_req = (options.dirsrv_cert_files, options.http_cert_files)
cert_file_opt = (options.pkinit_cert_files,) cert_file_opt = (options.pkinit_cert_files,)
@@ -1297,7 +1208,6 @@ def main():
domainlevel=options.domainlevel, domainlevel=options.domainlevel,
sid_generation_always=sid_generation_always, sid_generation_always=sid_generation_always,
random_serial_numbers=options._random_serial_numbers, random_serial_numbers=options._random_serial_numbers,
client_dns_over_tls=client_dns_over_tls
) )

View File

@@ -46,8 +46,7 @@ __all__ = ["IPAChangeConf", "certmonger", "sysrestore", "root_logger",
"check_available_memory", "getargspec", "get_min_idstart", "check_available_memory", "getargspec", "get_min_idstart",
"paths", "api", "ipautil", "adtrust_imported", "NUM_VERSION", "paths", "api", "ipautil", "adtrust_imported", "NUM_VERSION",
"time_service", "kra_imported", "dsinstance", "IPA_PYTHON_VERSION", "time_service", "kra_imported", "dsinstance", "IPA_PYTHON_VERSION",
"NUM_VERSION", "SerialNumber", "realm_to_ldapi_uri", "NUM_VERSION", "SerialNumber", "realm_to_ldapi_uri"]
"CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION"]
import sys import sys
import logging import logging
@@ -75,7 +74,6 @@ except ImportError:
return ArgSpec(args, varargs, varkw, defaults) return ArgSpec(args, varargs, varkw, defaults)
ANSIBLE_IPA_SERVER_MODULE_IMPORT_ERROR = None # pylint: disable=invalid-name
try: try:
from contextlib import contextmanager as contextlib_contextmanager from contextlib import contextmanager as contextlib_contextmanager
from ansible.module_utils import six from ansible.module_utils import six
@@ -218,29 +216,17 @@ try:
except ImportError: except ImportError:
SerialNumber = None SerialNumber = None
try:
# pylint: disable=invalid-name
CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION = False
# pylint: enable=invalid-name
from ipaclient.install.client import ClientInstallInterface
except ImportError:
pass
else:
if hasattr(ClientInstallInterface, "no_dnssec_validation"):
# pylint: disable=invalid-name
CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION = True
# pylint: enable=invalid-name
else: else:
# IPA version < 4.5 # IPA version < 4.5
raise RuntimeError("freeipa version '%s' is too old" % VERSION) raise RuntimeError("freeipa version '%s' is too old" % VERSION)
except ImportError as _err: except ImportError as _err:
# pylint: disable=invalid-name
ANSIBLE_IPA_SERVER_MODULE_IMPORT_ERROR = str(_err) ANSIBLE_IPA_SERVER_MODULE_IMPORT_ERROR = str(_err)
# pylint: enable=invalid-name
for attr in __all__: for attr in __all__:
setattr(sys.modules[__name__], attr, None) setattr(sys.modules[__name__], attr, None)
else:
ANSIBLE_IPA_SERVER_MODULE_IMPORT_ERROR = None
logger = logging.getLogger("ipa-server-install") logger = logging.getLogger("ipa-server-install")
@@ -255,13 +241,11 @@ def setup_logging():
@contextlib_contextmanager @contextlib_contextmanager
def redirect_stdout(stream): def redirect_stdout(stream):
old_stdout = sys.stdout
sys.stdout = stream sys.stdout = stream
try: try:
yield stream yield stream
finally: finally:
sys.stdout = old_stdout sys.stdout = sys.__stdout__
class AnsibleModuleLog(): class AnsibleModuleLog():

View File

@@ -1,42 +1,32 @@
--- ---
# tasks file for ipaserver # tasks file for ipaserver
- name: Install - Set ipaserver__dns_over_lts
ansible.builtin.set_fact:
ipaserver__dns_over_tls: "{{ ipaserver_dns_over_tls | default(ipaclient_dns_over_tls) | default(False) }}"
- name: Install - Package installation - name: Install - Package installation
when: ipaserver_install_packages | bool when: ipaserver_install_packages | bool
block: block:
- name: Install - Ensure that IPA server packages are installed
ansible.builtin.package:
name: "{{ ipaserver_packages }}"
state: present
- name: Install - Set packages for installation - name: Install - Ensure that IPA server packages for dns are installed
ansible.builtin.set_fact: ansible.builtin.package:
_ipapackages: "{{ ipaserver_packages }}" name: "{{ ipaserver_packages_dns }}"
state: present
- name: Install - Set packages for installlation, add DNS
ansible.builtin.set_fact:
_ipapackages: "{{ _ipapackages + ipaserver_packages_dns }}"
when: ipaserver_setup_dns | bool when: ipaserver_setup_dns | bool
- name: Install - Set packages for installlation, add DOT - name: Install - Ensure that IPA server packages for adtrust are installed
ansible.builtin.set_fact: ansible.builtin.package:
_ipapackages: "{{ _ipapackages + ipaserver_packages_dot }}" name: "{{ ipaserver_packages_adtrust }}"
when: ipaserver__dns_over_tls | bool state: present
- name: Install - Set packages for installlation, add adtrust
ansible.builtin.set_fact:
_ipapackages: "{{ _ipapackages + ipaserver_packages_adtrust }}"
when: ipaserver_setup_adtrust | bool when: ipaserver_setup_adtrust | bool
- name: Install - Set packages for installlation, add firewalld - name: Install - Ensure that firewall packages installed
ansible.builtin.set_fact: ansible.builtin.package:
_ipapackages: "{{ _ipapackages + ipaserver_packages_firewalld }}" name: "{{ ipaserver_packages_firewalld }}"
state: present
when: ipaserver_setup_firewalld | bool when: ipaserver_setup_firewalld | bool
- name: Install - Ensure that packages are installed
ansible.builtin.package:
name: "{{ _ipapackages }}"
state: present
- name: Install - Firewall configuration - name: Install - Firewall configuration
when: ipaserver_setup_firewalld | bool when: ipaserver_setup_firewalld | bool
@@ -47,21 +37,20 @@
enabled: yes enabled: yes
state: started state: started
- name: Firewalld - verify zones - name: Firewalld - Verify runtime zone "{{ ipaserver_firewalld_zone }}"
ansible.builtin.shell: >
firewall-cmd
--info-zone="{{ ipaserver_firewalld_zone }}"
>/dev/null
when: ipaserver_firewalld_zone is defined when: ipaserver_firewalld_zone is defined
block:
- name: Firewalld - Verify runtime zone from ipaserver_firewalld_zone
ansible.builtin.shell: >
firewall-cmd
--info-zone="{{ ipaserver_firewalld_zone }}"
>/dev/null
- name: Firewalld - Verify permanent zone from ipaserver_firewalld_zone - name: Firewalld - Verify permanent zone "{{ ipaserver_firewalld_zone }}"
ansible.builtin.shell: > ansible.builtin.shell: >
firewall-cmd firewall-cmd
--permanent --permanent
--info-zone="{{ ipaserver_firewalld_zone }}" --info-zone="{{ ipaserver_firewalld_zone }}"
>/dev/null >/dev/null
when: ipaserver_firewalld_zone is defined
- name: Copy external certs - name: Copy external certs
ansible.builtin.include_tasks: "{{ role_path }}/tasks/copy_external_cert.yml" ansible.builtin.include_tasks: "{{ role_path }}/tasks/copy_external_cert.yml"
@@ -132,11 +121,6 @@
auto_forwarders: "{{ ipaserver_auto_forwarders }}" auto_forwarders: "{{ ipaserver_auto_forwarders }}"
forward_policy: "{{ ipaserver_forward_policy | default(omit) }}" forward_policy: "{{ ipaserver_forward_policy | default(omit) }}"
no_dnssec_validation: "{{ ipaserver_no_dnssec_validation }}" no_dnssec_validation: "{{ ipaserver_no_dnssec_validation }}"
dot_forwarders: "{{ ipaserver_dot_forwarders | default([]) }}"
dns_over_tls: "{{ ipaserver__dns_over_tls }}"
dns_over_tls_cert: "{{ ipaserver_dns_over_tls_cert | default(omit) }}"
dns_over_tls_key: "{{ ipaserver_dns_over_tls_key | default(omit) }}"
dns_policy: "{{ ipaserver_dns_policy | default(omit) }}"
### ad trust ### ### ad trust ###
enable_compat: "{{ ipaserver_enable_compat }}" enable_compat: "{{ ipaserver_enable_compat }}"
netbios_name: "{{ ipaserver_netbios_name | default(omit) }}" netbios_name: "{{ ipaserver_netbios_name | default(omit) }}"
@@ -208,11 +192,6 @@
auto_forwarders: "{{ ipaserver_auto_forwarders }}" auto_forwarders: "{{ ipaserver_auto_forwarders }}"
forward_policy: "{{ ipaserver_forward_policy | default(omit) }}" forward_policy: "{{ ipaserver_forward_policy | default(omit) }}"
no_dnssec_validation: "{{ ipaserver_no_dnssec_validation }}" no_dnssec_validation: "{{ ipaserver_no_dnssec_validation }}"
dot_forwarders: "{{ ipaserver_dot_forwarders | default([]) }}"
dns_over_tls: "{{ ipaserver__dns_over_tls }}"
dns_over_tls_cert: "{{ ipaserver_dns_over_tls_cert | default(omit) }}"
dns_over_tls_key: "{{ ipaserver_dns_over_tls_key | default(omit) }}"
dns_policy: "{{ ipaserver_dns_policy | default(omit) }}"
### ad trust ### ### ad trust ###
enable_compat: "{{ ipaserver_enable_compat }}" enable_compat: "{{ ipaserver_enable_compat }}"
netbios_name: "{{ ipaserver_netbios_name | default(omit) }}" netbios_name: "{{ ipaserver_netbios_name | default(omit) }}"
@@ -402,11 +381,6 @@
forward_policy: "{{ result_ipaserver_prepare.forward_policy }}" forward_policy: "{{ result_ipaserver_prepare.forward_policy }}"
zonemgr: "{{ ipaserver_zonemgr | default(omit) }}" zonemgr: "{{ ipaserver_zonemgr | default(omit) }}"
no_dnssec_validation: "{{ result_ipaserver_prepare.no_dnssec_validation }}" no_dnssec_validation: "{{ result_ipaserver_prepare.no_dnssec_validation }}"
dot_forwarders: "{{ ipaserver_dot_forwarders | default([]) }}"
dns_over_tls: "{{ ipaserver__dns_over_tls }}"
dns_over_tls_cert: "{{ ipaserver_dns_over_tls_cert | default(omit) }}"
dns_over_tls_key: "{{ ipaserver_dns_over_tls_key | default(omit) }}"
dns_policy: "{{ ipaserver_dns_policy | default(omit) }}"
### additional ### ### additional ###
dns_ip_addresses: "{{ result_ipaserver_prepare.dns_ip_addresses }}" dns_ip_addresses: "{{ result_ipaserver_prepare.dns_ip_addresses }}"
dns_reverse_zones: "{{ result_ipaserver_prepare.dns_reverse_zones }}" dns_reverse_zones: "{{ result_ipaserver_prepare.dns_reverse_zones }}"
@@ -458,7 +432,6 @@
ipaclient_no_ntp: ipaclient_no_ntp:
"{{ 'true' if result_ipaserver_test.ipa_python_version >= 40690 "{{ 'true' if result_ipaserver_test.ipa_python_version >= 40690
else 'false' }}" else 'false' }}"
ipaclient_dns_over_tls: "{{ result_ipaserver_test.client_dns_over_tls }}"
ipaclient_install_packages: no ipaclient_install_packages: no
- name: Install - Enable IPA - name: Install - Enable IPA
@@ -479,8 +452,6 @@
{{ "--add-service=freeipa-trust" if ipaserver_setup_adtrust | bool {{ "--add-service=freeipa-trust" if ipaserver_setup_adtrust | bool
else "" }} else "" }}
{{ "--add-service=dns" if ipaserver_setup_dns | bool else "" }} {{ "--add-service=dns" if ipaserver_setup_dns | bool else "" }}
{{ "--add-service=dns-over-tls" if ipaserver__dns_over_tls | bool
else "" }}
{{ "--add-service=ntp" if not ipaclient_no_ntp | bool else "" }} {{ "--add-service=ntp" if not ipaclient_no_ntp | bool else "" }}
when: ipaserver_setup_firewalld | bool when: ipaserver_setup_firewalld | bool
@@ -494,8 +465,6 @@
{{ "--add-service=freeipa-trust" if ipaserver_setup_adtrust | bool {{ "--add-service=freeipa-trust" if ipaserver_setup_adtrust | bool
else "" }} else "" }}
{{ "--add-service=dns" if ipaserver_setup_dns | bool else "" }} {{ "--add-service=dns" if ipaserver_setup_dns | bool else "" }}
{{ "--add-service=dns-over-tls" if ipaserver__dns_over_tls | bool
else "" }}
{{ "--add-service=ntp" if not ipaclient_no_ntp | bool else "" }} {{ "--add-service=ntp" if not ipaclient_no_ntp | bool else "" }}
when: ipaserver_setup_firewalld | bool when: ipaserver_setup_firewalld | bool

View File

@@ -3,6 +3,5 @@
--- ---
ipaserver_packages: [ "freeipa-server", "python3-libselinux" ] ipaserver_packages: [ "freeipa-server", "python3-libselinux" ]
ipaserver_packages_dns: [ "freeipa-server-dns" ] ipaserver_packages_dns: [ "freeipa-server-dns" ]
ipaserver_packages_dot: [ "freeipa-server-encrypted-dns" ]
ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ] ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ]
ipaserver_packages_firewalld: [ "firewalld" ] ipaserver_packages_firewalld: [ "firewalld" ]

View File

@@ -3,6 +3,5 @@
--- ---
ipaserver_packages: [ "ipa-server", "libselinux-python" ] ipaserver_packages: [ "ipa-server", "libselinux-python" ]
ipaserver_packages_dns: [ "ipa-server-dns" ] ipaserver_packages_dns: [ "ipa-server-dns" ]
ipaserver_packages_dot: [ ]
ipaserver_packages_adtrust: [ "ipa-server-trust-ad" ] ipaserver_packages_adtrust: [ "ipa-server-trust-ad" ]
ipaserver_packages_firewalld: [ "firewalld" ] ipaserver_packages_firewalld: [ "firewalld" ]

View File

@@ -3,6 +3,5 @@
--- ---
ipaserver_packages: [ "@idm:DL1/server" ] ipaserver_packages: [ "@idm:DL1/server" ]
ipaserver_packages_dns: [ "@idm:DL1/dns" ] ipaserver_packages_dns: [ "@idm:DL1/dns" ]
ipaserver_packages_dot: [ ]
ipaserver_packages_adtrust: [ "@idm:DL1/adtrust" ] ipaserver_packages_adtrust: [ "@idm:DL1/adtrust" ]
ipaserver_packages_firewalld: [ "firewalld" ] ipaserver_packages_firewalld: [ "firewalld" ]

View File

@@ -2,7 +2,6 @@
--- ---
ipaserver_packages: [ "freeipa-server" ] ipaserver_packages: [ "freeipa-server" ]
ipaserver_packages_dns: [ "freeipa-server-dns" ] ipaserver_packages_dns: [ "freeipa-server-dns" ]
ipaserver_packages_dot: [ ]
ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ] ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ]
ipaserver_packages_firewalld: [ "firewalld" ] ipaserver_packages_firewalld: [ "firewalld" ]
# Ubuntu Bionic Beaver must use python2 as Python interpreter due # Ubuntu Bionic Beaver must use python2 as Python interpreter due

View File

@@ -3,6 +3,5 @@
--- ---
ipaserver_packages: [ "freeipa-server" ] ipaserver_packages: [ "freeipa-server" ]
ipaserver_packages_dns: [ "freeipa-server-dns" ] ipaserver_packages_dns: [ "freeipa-server-dns" ]
ipaserver_packages_dot: [ ]
ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ] ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ]
ipaserver_packages_firewalld: [ "firewalld" ] ipaserver_packages_firewalld: [ "firewalld" ]

View File

@@ -3,6 +3,5 @@
--- ---
ipaserver_packages: [ "ipa-server", "python3-libselinux" ] ipaserver_packages: [ "ipa-server", "python3-libselinux" ]
ipaserver_packages_dns: [ "ipa-server-dns" ] ipaserver_packages_dns: [ "ipa-server-dns" ]
ipaserver_packages_dot: [ "ipa-server-encrypted-dns" ]
ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ] ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ]
ipaserver_packages_firewalld: [ "firewalld" ] ipaserver_packages_firewalld: [ "firewalld" ]

View File

@@ -61,12 +61,7 @@ good-names =
dt, ca, dt, ca,
# These are utils tools, and not part of the released collection. # These are utils tools, and not part of the released collection.
galaxyfy-playbook, galaxyfy-README, galaxyfy-module-EXAMPLES, galaxyfy-playbook, galaxyfy-README, galaxyfy-module-EXAMPLES,
module_EXAMPLES, module_EXAMPLES
MODULE_IMPORT_ERROR, ANSIBLE_IPA_CLIENT_MODULE_IMPORT_ERROR,
CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION, ANSIBLE_IPA_REPLICA_MODULE_IMPORT_ERROR,
SYSTEMD_RESOLVED_IPA_CONF, ANSIBLE_IPA_SERVER_MODULE_IMPORT_ERROR,
NETWORK_MANAGER_IPA_CONF, ANSIBLE_FREEIPA_MODULE_IMPORT_ERROR,
FIX_6741_DEEPCOPY_OBJECTCLASSES
[pylint.IMPORTS] [pylint.IMPORTS]
@@ -89,7 +84,6 @@ ignored-modules =
[pylint.DESIGN] [pylint.DESIGN]
max-attributes=12 max-attributes=12
max-public-methods=25
[pylint.REFACTORING] [pylint.REFACTORING]
max-nested-blocks = 9 max-nested-blocks = 9

View File

@@ -66,7 +66,7 @@
recurse: no recurse: no
file_type: directory file_type: directory
register: result register: result
failed_when: result.files | length == 0 failed_when: not result.files
# Test backup and copy to controller, don't keep copy on server # Test backup and copy to controller, don't keep copy on server
- name: Remove all backup from server. - name: Remove all backup from server.
@@ -108,7 +108,7 @@
recurse: no recurse: no
file_type: directory file_type: directory
register: backups register: backups
failed_when: backups.files | length > 0 failed_when: backups.files
- name: Verify backup on controller. - name: Verify backup on controller.
ansible.builtin.find: ansible.builtin.find:
@@ -116,7 +116,7 @@
pattern: "{{ ansible_facts.fqdn }}*" pattern: "{{ ansible_facts.fqdn }}*"
file_type: directory file_type: directory
register: backups register: backups
failed_when: backups.files | length == 0 failed_when: not backups.files
delegate_to: localhost delegate_to: localhost
become: no become: no
@@ -161,7 +161,7 @@
recurse: no recurse: no
file_type: directory file_type: directory
register: result register: result
failed_when: result.files | length == 0 failed_when: not result.files
- name: Verify backup on controller. - name: Verify backup on controller.
ansible.builtin.find: ansible.builtin.find:
@@ -169,7 +169,7 @@
pattern: "{{ ansible_facts.fqdn }}*" pattern: "{{ ansible_facts.fqdn }}*"
file_type: directory file_type: directory
register: backups register: backups
failed_when: backups.files | length == 0 failed_when: not backups.files
delegate_to: localhost delegate_to: localhost
become: no become: no
@@ -214,7 +214,7 @@
pattern: "{{ ansible_facts.fqdn }}*" pattern: "{{ ansible_facts.fqdn }}*"
file_type: directory file_type: directory
register: backups register: backups
failed_when: backups.files | length == 0 failed_when: not backups.files
delegate_to: localhost delegate_to: localhost
become: no become: no
@@ -232,7 +232,7 @@
pattern: "{{ ansible_facts.fqdn }}*" pattern: "{{ ansible_facts.fqdn }}*"
file_type: directory file_type: directory
register: backups register: backups
failed_when: backups.files | length == 0 failed_when: not backups.files
delegate_to: localhost delegate_to: localhost
become: no become: no
@@ -252,7 +252,7 @@
path: /var/lib/ipa/backup path: /var/lib/ipa/backup
file_type: directory file_type: directory
register: backups register: backups
failed_when: backups.files | length == 0 failed_when: not backups.files
# Copy backup from server to controller # Copy backup from server to controller
- name: List all existing backups on controller - name: List all existing backups on controller
@@ -280,7 +280,7 @@
recurse: no recurse: no
file_type: directory file_type: directory
register: server_backups register: server_backups
failed_when: server_backups.files | length == 0 failed_when: not server_backups.files
- name: Copy backup from server to controller. - name: Copy backup from server to controller.
ansible.builtin.include_role: ansible.builtin.include_role:
@@ -300,7 +300,7 @@
pattern: "{{ ansible_facts.fqdn }}*" pattern: "{{ ansible_facts.fqdn }}*"
file_type: directory file_type: directory
register: backups register: backups
failed_when: backups.files | length == 0 failed_when: not backups.files
delegate_to: localhost delegate_to: localhost
become: no become: no
@@ -311,7 +311,7 @@
recurse: no recurse: no
file_type: directory file_type: directory
register: backups register: backups
failed_when: backups.files | length == 0 failed_when: not backups.files
- name: Remov all backup from server. - name: Remov all backup from server.
ansible.builtin.include_role: ansible.builtin.include_role:
@@ -326,7 +326,7 @@
recurse: no recurse: no
file_type: directory file_type: directory
register: backups register: backups
failed_when: backups.files | length > 0 failed_when: backups.files
# Remove all backups from server # Remove all backups from server
- name: Create a backup on the server - name: Create a backup on the server
@@ -348,7 +348,7 @@
recurse: no recurse: no
file_type: directory file_type: directory
register: backups register: backups
failed_when: backups.files | length > 0 failed_when: backups.files
# Remove all backup from server # Remove all backup from server
- name: Remove all backup from server. - name: Remove all backup from server.
@@ -370,7 +370,7 @@
recurse: no recurse: no
file_type: directory file_type: directory
register: server_backups register: server_backups
failed_when: server_backups.files | length == 0 failed_when: not server_backups.files
- name: Remove backup from server. - name: Remove backup from server.
ansible.builtin.include_role: ansible.builtin.include_role:
@@ -406,7 +406,7 @@
recurse: no recurse: no
file_type: directory file_type: directory
register: server_backups register: server_backups
failed_when: server_backups.files | length > 0 failed_when: server_backups.files
# CLEANUP # CLEANUP

View File

@@ -124,7 +124,7 @@
reason: 9 reason: 9
state: revoked state: revoked
register: result register: result
failed_when: not (result.failed and ("Request failed with status 404" in result.msg or result.msg is regex("Certificate [^0]*0x123456789 not found"))) failed_when: not (result.failed and ("Request failed with status 404" in result.msg or "Certificate serial number 0x123456789 not found" in result.msg))
- name: Try to release revoked certificate - name: Try to release revoked certificate
ipacert: ipacert:
@@ -140,7 +140,7 @@
certificate_out: "/root/cert_1.pem" certificate_out: "/root/cert_1.pem"
state: requested state: requested
register: result register: result
failed_when: not result.changed or result.failed or result.certificate != {} failed_when: not result.changed or result.failed or result.certificate
- name: Check requested certificate file - name: Check requested certificate file
ansible.builtin.file: ansible.builtin.file:
@@ -155,7 +155,7 @@
certificate_out: "/root/retrieved.pem" certificate_out: "/root/retrieved.pem"
state: retrieved state: retrieved
register: result register: result
failed_when: result.changed or result.failed or result.certificate != {} failed_when: result.changed or result.failed or result.certificate
- name: Check retrieved certificate file - name: Check retrieved certificate file
ansible.builtin.file: ansible.builtin.file:
@@ -194,7 +194,7 @@
profile: invalid_profile profile: invalid_profile
state: requested state: requested
register: result register: result
failed_when: not (result.failed and ("Request failed with status 400" in result.msg or "Profile not found" in result.msg)) failed_when: not (result.failed and "Request failed with status 400" in result.msg)
# CLEANUP TEST ITEMS # CLEANUP TEST ITEMS

View File

@@ -136,7 +136,7 @@
reason: 9 reason: 9
state: revoked state: revoked
register: result register: result
failed_when: not (result.failed and ("Request failed with status 404" in result.msg or result.msg is regex("Certificate [^0]*0x123456789 not found"))) failed_when: not (result.failed and ("Request failed with status 404" in result.msg or "Certificate serial number 0x123456789 not found" in result.msg))
- name: Try to release revoked certificate - name: Try to release revoked certificate
ipacert: ipacert:
@@ -153,7 +153,7 @@
certificate_out: "/root/cert_1.pem" certificate_out: "/root/cert_1.pem"
state: requested state: requested
register: result register: result
failed_when: not result.changed or result.failed or result.certificate != {} failed_when: not result.changed or result.failed or result.certificate
- name: Check requested certificate file - name: Check requested certificate file
ansible.builtin.file: ansible.builtin.file:
@@ -168,7 +168,7 @@
certificate_out: "/root/retrieved.pem" certificate_out: "/root/retrieved.pem"
state: retrieved state: retrieved
register: result register: result
failed_when: result.changed or result.failed or result.certificate != {} failed_when: result.changed or result.failed or result.certificate
- name: Check retrieved certificate file - name: Check retrieved certificate file
ansible.builtin.file: ansible.builtin.file:
@@ -207,7 +207,7 @@
profile: invalid_profile profile: invalid_profile
state: requested state: requested
register: result register: result
failed_when: not (result.failed and ("Request failed with status 400" in result.msg or "Profile not found" in result.msg)) failed_when: not (result.failed and "Request failed with status 400" in result.msg)
# CLEANUP TEST ITEMS # CLEANUP TEST ITEMS

View File

@@ -123,7 +123,7 @@
reason: 9 reason: 9
state: revoked state: revoked
register: result register: result
failed_when: not (result.failed and ("Request failed with status 404" in result.msg or result.msg is regex("Certificate [^0]*0x123456789 not found"))) failed_when: not (result.failed and ("Request failed with status 404" in result.msg or "Certificate serial number 0x123456789 not found" in result.msg))
- name: Try to release revoked certificate - name: Try to release revoked certificate
ipacert: ipacert:
@@ -140,7 +140,7 @@
certificate_out: "/root/cert_1.pem" certificate_out: "/root/cert_1.pem"
state: requested state: requested
register: result register: result
failed_when: not result.changed or result.failed or result.certificate != {} failed_when: not result.changed or result.failed or result.certificate
- name: Check requested certificate file - name: Check requested certificate file
ansible.builtin.file: ansible.builtin.file:
@@ -155,7 +155,7 @@
certificate_out: "/root/retrieved.pem" certificate_out: "/root/retrieved.pem"
state: retrieved state: retrieved
register: result register: result
failed_when: result.changed or result.failed or result.certificate != {} failed_when: result.changed or result.failed or result.certificate
- name: Check retrieved certificate file - name: Check retrieved certificate file
ansible.builtin.file: ansible.builtin.file:
@@ -194,7 +194,7 @@
profile: invalid_profile profile: invalid_profile
state: requested state: requested
register: result register: result
failed_when: not (result.failed and ("Request failed with status 400" in result.msg or "Profile not found" in result.msg)) failed_when: not (result.failed and "Request failed with status 400" in result.msg)
# CLEANUP TEST ITEMS # CLEANUP TEST ITEMS

View File

@@ -34,6 +34,16 @@
ipaapi_context: "{{ ipa_context | default(omit) }}" ipaapi_context: "{{ ipa_context | default(omit) }}"
emaildomain: ipa.test emaildomain: ipa.test
- name: Ensure the default e-mail domain cannot be set to an invalid email domain.
ipaconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
emaildomain: invalid@emaildomain
register: invalid_emaildomain
failed_when:
invalid_emaildomain.changed
or not (invalid_emaildomain.failed and "Invalid 'emaildomain' value:" in invalid_emaildomain.msg)
- name: Set default shell to '/bin/sh' - name: Set default shell to '/bin/sh'
ipaconfig: ipaconfig:
ipaadmin_password: SomeADMINpassword ipaadmin_password: SomeADMINpassword
@@ -400,7 +410,7 @@
searchrecordslimit: '{{ previousconfig.config.searchrecordslimit | default(100) | int }}' searchrecordslimit: '{{ previousconfig.config.searchrecordslimit | default(100) | int }}'
usersearch: '{{ previousconfig.config.usersearch | default(omit) }}' usersearch: '{{ previousconfig.config.usersearch | default(omit) }}'
groupsearch: '{{ previousconfig.config.groupsearch | default(omit) }}' groupsearch: '{{ previousconfig.config.groupsearch | default(omit) }}'
enable_migration: '{{ omit if previousconfig.config.enable_migration is not defined else (previousconfig.config.enable_migration | bool) }}' enable_migration: '{{ previousconfig.config.enable_migration | default(False) | bool }}'
groupobjectclasses: '{{ previousconfig.config.groupobjectclasses | default(omit) }}' groupobjectclasses: '{{ previousconfig.config.groupobjectclasses | default(omit) }}'
userobjectclasses: '{{ previousconfig.config.userobjectclasses | default(omit) }}' userobjectclasses: '{{ previousconfig.config.userobjectclasses | default(omit) }}'
pwdexpnotify: '{{ previousconfig.config.pwdexpnotify | default(4) | int }}' pwdexpnotify: '{{ previousconfig.config.pwdexpnotify | default(4) | int }}'
@@ -436,7 +446,7 @@
searchrecordslimit: '{{ previousconfig.config.searchrecordslimit | default(omit) | int }}' searchrecordslimit: '{{ previousconfig.config.searchrecordslimit | default(omit) | int }}'
usersearch: '{{ previousconfig.config.usersearch | default(omit) }}' usersearch: '{{ previousconfig.config.usersearch | default(omit) }}'
groupsearch: '{{ previousconfig.config.groupsearch | default(omit) }}' groupsearch: '{{ previousconfig.config.groupsearch | default(omit) }}'
enable_migration: '{{ omit if previousconfig.config.enable_migration is not defined else (previousconfig.config.enable_migration | bool) }}' enable_migration: '{{ previousconfig.config.enable_migration | default(omit) | bool }}'
groupobjectclasses: '{{ previousconfig.config.groupobjectclasses | default(omit) }}' groupobjectclasses: '{{ previousconfig.config.groupobjectclasses | default(omit) }}'
userobjectclasses: '{{ previousconfig.config.userobjectclasses | default(omit) }}' userobjectclasses: '{{ previousconfig.config.userobjectclasses | default(omit) }}'
pwdexpnotify: '{{ previousconfig.config.pwdexpnotify | default(omit) | int }}' pwdexpnotify: '{{ previousconfig.config.pwdexpnotify | default(omit) | int }}'
@@ -473,7 +483,7 @@
searchrecordslimit: '{{ previousconfig.config.searchrecordslimit | default(omit) | int }}' searchrecordslimit: '{{ previousconfig.config.searchrecordslimit | default(omit) | int }}'
usersearch: '{{ previousconfig.config.usersearch | default(omit) }}' usersearch: '{{ previousconfig.config.usersearch | default(omit) }}'
groupsearch: '{{ previousconfig.config.groupsearch | default(omit) }}' groupsearch: '{{ previousconfig.config.groupsearch | default(omit) }}'
enable_migration: '{{ omit if previousconfig.config.enable_migration is not defined else (previousconfig.config.enable_migration | bool) }}' enable_migration: '{{ previousconfig.config.enable_migration | default(omit) | bool }}'
groupobjectclasses: '{{ previousconfig.config.groupobjectclasses | default(omit) }}' groupobjectclasses: '{{ previousconfig.config.groupobjectclasses | default(omit) }}'
userobjectclasses: '{{ previousconfig.config.userobjectclasses | default(omit) }}' userobjectclasses: '{{ previousconfig.config.userobjectclasses | default(omit) }}'
pwdexpnotify: '{{ previousconfig.config.pwdexpnotify | default(omit) | int }}' pwdexpnotify: '{{ previousconfig.config.pwdexpnotify | default(omit) | int }}'

View File

@@ -5,8 +5,6 @@
gather_facts: no gather_facts: no
tasks: tasks:
- name: Include tasks ../env_freeipa_facts.yml
ansible.builtin.include_tasks: ../env_freeipa_facts.yml
# GET CURRENT CONFIG # GET CURRENT CONFIG
@@ -82,36 +80,6 @@
register: result register: result
failed_when: result.changed or result.failed failed_when: result.changed or result.failed
- name: Ensure config with user_auth_type passkey
ipaconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
user_auth_type:
- passkey
register: result
failed_when: not result.changed or result.failed
when: passkey_is_supported
- name: Ensure config with user_auth_type passkey, again
ipaconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
user_auth_type:
- passkey
register: result
failed_when: result.changed or result.failed
when: passkey_is_supported
- name: Check if correct message is given if passkey is not supported.
ipaconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
user_auth_type:
- passkey
register: result
failed_when: not result.failed or "'passkey' is not supported" not in result.msg
when: not passkey_is_supported
- name: Ensure config with empty user_auth_type - name: Ensure config with empty user_auth_type
ipaconfig: ipaconfig:
ipaadmin_password: SomeADMINpassword ipaadmin_password: SomeADMINpassword
@@ -170,6 +138,6 @@
ipaconfig: ipaconfig:
ipaadmin_password: SomeADMINpassword ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}" ipaapi_context: "{{ ipa_context | default(omit) }}"
pac_type: '{{ previousconfig.config.pac_type | default("") }}' pac_type: '{{ previousconfig.config.pac_type }}'
user_auth_type: '{{ previousconfig.config.user_auth_type | default("") }}' user_auth_type: '{{ previousconfig.config.user_auth_type }}'
configstring: '{{ previousconfig.config.configstring | default("") }}' configstring: '{{ previousconfig.config.configstring }}'

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