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: '**/env_*.yml'
# parseable: true
parseable: true
quiet: false

View File

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

View File

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

View File

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

View File

@@ -145,7 +145,7 @@ Variable | Description | Required
`selinuxusermaporder` \| `ipaselinuxusermaporder`| Set ordered list in increasing priority of SELinux users | no
`selinuxusermapdefault`\| `ipaselinuxusermapdefault` | Set default SELinux user when no match is found in SELinux map rule | no
`pac_type` \| `ipakrbauthzdata` | set default types of PAC supported for services (choices: `MS-PAC`, `PAD`, `nfs:NONE`). Use `""` to clear this variable. | no
`user_auth_type` \| `ipauserauthtype` | set default types of supported user authentication (choices: `password`, `radius`, `otp`, `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
`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

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
=========
@@ -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
`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
`rename` \| `new_name` | Rename the group 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
`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`, `renamed` or `query`, default: `present`. | yes
`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 on of `member` or `group` and defaults to `group`. | no
`state` | The state to ensure. It can be one of `present`, `absent` or `renamed`, default: `present`. | yes
Authors

View File

@@ -354,7 +354,7 @@ Variable | Description | Required
`mac_address` \| `macaddress` | List of hardware MAC addresses. | no
`sshpubkey` \| `ipasshpubkey` | List of SSH public keys | 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
`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

View File

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

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
service:
- service01
sysaccount:
- my-app
action: member
state: absent
```
@@ -255,8 +253,7 @@ Variable | Description | Required
`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
`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 one of `member` or `role` and defaults to `role`. | no
`action` | Work on role or member level. It can be on of `member` or `role` and defaults to `role`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no
@@ -264,4 +261,3 @@ Authors
=======
Rafael Jeffman
Thomas Woerner

View File

@@ -361,7 +361,7 @@ Variable | Description | Required
-------- | ----------- | --------
`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
`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
`ok_as_delegate` \| `ipakrbokasdelegate` | Client credentials may be delegated to the service. Default to false. (bool) | no
`ok_to_auth_as_delegate` \| `ipakrboktoauthasdelegate` | The service is allowed to authenticate on behalf of a client. Default to false. (bool) | no

View File

@@ -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
```
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.
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
`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
`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
`carlicense` | List of car licenses. | 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
`radius` | RADIUS proxy configuration | no
`radiususer` | RADIUS proxy username | no

View File

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

View File

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

View File

@@ -10,26 +10,15 @@ schedules:
trigger: none
pool:
vmImage: 'ubuntu-24.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" }
vmImage: 'ubuntu-20.04'
variables:
distros: "fedora-latest,c10s,c9s,fedora-rawhide"
ansible_version: "-core >=2.18,<2.19"
# We need to have two sets, as c8s is not supported by all ansible versions
recent_distros: "fedora-latest,fedora-rawhide,c10s,c9s"
distros: "fedora-latest,fedora-rawhide,c10s,c9s,c8s"
ansible_latest: "-core"
ansible_minimum: "-core <2.16"
ansible_version: "-core >=2.16,<2.17"
stages:
@@ -49,7 +38,7 @@ stages:
# Latest ansible
- ${{ each distro in split(variables.distros, ',') }}:
- ${{ each distro in split(variables.recent_distros, ',') }}:
- stage: ${{ replace(distro, '-', '_') }}_ansible_latest
dependsOn: []
jobs:
@@ -61,44 +50,30 @@ stages:
skip_git_test: true
test_galaxy: false
# Galaxy with Latest ansible
# Selected ansible-core version
- ${{ each distro in split(variables.distros, ',') }}:
- stage: galaxy_${{ replace(distro, '-', '_') }}_ansible_latest
- stage: ${{ replace(distro, '-', '_') }}_ansible_2_16
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
distro: ${{ distro }}
ansible_version: ${{ variables.ansible_latest }}
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 }}
ansible_version: ${{ variables.ansible_version }}
skip_git_test: true
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 }}:
- stage: galaxy_${{ config.distro }}_distro_ansible_${{ replace(config.version_name, '.', '_') }}
- ${{ each distro in split(variables.distros, ',') }}:
- stage: galaxy_${{ replace(distro, '-', '_') }}_asible_2_16
dependsOn: []
jobs:
- template: templates/group_tests.yml
parameters:
build_number: $(Build.BuildNumber)
distro: ${{ config.distro }}
ansible_version: -core${{ config.ansible_version }}
distro: ${{ distro }}
ansible_version: ${{ variables.ansible_version }}
skip_git_test: true
test_galaxy: true

View File

@@ -3,31 +3,18 @@ trigger:
- master
pool:
vmImage: 'ubuntu-24.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" }
vmImage: 'ubuntu-20.04'
variables:
distros: "fedora-latest,c10s,c9s,fedora-rawhide"
ansible_version: "-core >=2.18,<2.19"
distros: "fedora-latest,c10s,c9s,c8s,fedora-rawhide"
ansible_version: "-core >=2.15,<2.16"
stages:
# Test with repository in all "current" distros
# Test with repository in all distros
- ${{ each distro in split(variables.distros, ',') }}:
- stage: ${{ replace(distro, '-', '_') }}_ansible_2_18
- stage: ${{ replace(distro, '-', '_') }}_ansible_2_16
dependsOn: []
jobs:
- template: templates/run_tests.yml
@@ -40,7 +27,7 @@ stages:
# Galaxy on Fedora
- stage: galaxy_fedora_latest_ansible_2_18
- stage: galaxy_fedora_latest_ansible_2_16
dependsOn: []
jobs:
- template: templates/run_tests.yml
@@ -50,18 +37,3 @@ stages:
ansible_version: ${{ variables.ansible_version }}
skip_git_test: false
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: |
git fetch --unshallow
utils/build-collection.sh -i rpm
utils/build-galaxy-release.sh -i
retryCountOnTaskFailure: 5
displayName: Build Galaxy release
condition: ${{ parameters.test_galaxy }}

View File

@@ -119,6 +119,13 @@ then
deployed=true
fi
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}"

View File

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

View File

@@ -12,7 +12,6 @@ dnf --assumeyes install \
bash \
systemd \
procps-ng \
hostname \
iproute; \
dnf clean all; \
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/fixipaip.service /etc/systemd/system/
RUN chmod +x /root/fixnet.sh /root/fixipaip.sh
RUN systemctl enable fixnet.service
RUN systemctl enable fixipaip.service
STOPSIGNAL RTMIN+3

View File

@@ -9,7 +9,6 @@ dnf --assumeyes install \
bash \
systemd \
procps-ng \
hostname \
iproute; \
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/fixipaip.service /etc/systemd/system/
RUN chmod +x /root/fixnet.sh /root/fixipaip.sh
RUN systemctl enable fixnet.service
RUN systemctl enable fixipaip.service
STOPSIGNAL RTMIN+3

View File

@@ -11,7 +11,6 @@ dnf --assumeyes install \
bash \
systemd \
procps-ng \
hostname \
iproute; \
dnf clean all; \
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/fixipaip.service /etc/systemd/system/
RUN chmod +x /root/fixnet.sh /root/fixipaip.sh
RUN systemctl enable fixnet.service
RUN systemctl enable fixipaip.service
STOPSIGNAL RTMIN+3

View File

@@ -11,7 +11,6 @@ dnf --assumeyes install \
bash \
systemd \
procps-ng \
hostname \
iproute; \
dnf clean all; \
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/fixipaip.service /etc/systemd/system/
RUN chmod +x /root/fixnet.sh /root/fixipaip.sh
RUN systemctl enable fixnet.service
RUN systemctl enable fixipaip.service
STOPSIGNAL RTMIN+3

View File

@@ -4,20 +4,13 @@
SCRIPTDIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")"
TOPDIR="$(readlink -f "${SCRIPTDIR}/../..")"
# shellcheck disable=SC1091
. "${SCRIPTDIR}/shdefaults"
# shellcheck disable=SC1091
. "${TOPDIR}/utils/shfun"
container_create() {
local name=${1}
local image=${2}
shift 2
declare -a extra_opts
readarray -t extra_opts < \
<(sed -e "s/-/--cap-drop=/g" -e "s/+/--cap-add=/g" \
<<< "$(printf '%s\n' "${CAP_DEFAULTS[@]}")")
declare -a extra_opts=()
for opt in "$@"
do
[ -z "${opt}" ] && continue
@@ -26,7 +19,6 @@ container_create() {
cpus=*) extra_opts+=("--${opt}") ;;
memory=*) extra_opts+=("--${opt}") ;;
capabilities=*) extra_opts+=("--cap-add=${opt##*=}") ;;
volume=*) extra_opts+=("--volume=${opt##*=}") ;;
*) log error "container_create: Invalid option: ${opt}" ;;
esac
done
@@ -55,19 +47,6 @@ container_start() {
log info "= Starting ${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
}
@@ -216,34 +195,3 @@ container_fetch() {
podman cp "${name}:${source}" "${destination}"
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]
Description=Fix IPA server IP in IPA Server
After=ipa.service
PartOf=ipa.service
[Service]
Type=oneshot
@@ -10,4 +9,4 @@ StandardOutput=journal
StandardError=journal
[Install]
WantedBy=ipa.service
WantedBy=default.target

View File

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

View File

@@ -1,5 +1,8 @@
[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]
Type=oneshot
@@ -8,4 +11,4 @@ StandardOutput=journal
StandardError=journal
[Install]
WantedBy=container-ipa.target
WantedBy=ipa.service

View File

@@ -39,35 +39,26 @@ if [ -z "${IP}" ] || ! valid_ipv4 "${IP}" ; then
exit 1
fi
DOMAIN=${HOSTNAME#*.}
echo "Fix NET:"
echo " HOSTNAME: '${HOSTNAME}'"
echo " DOMAIN: '${DOMAIN}'"
echo " IP: '${IP}'"
echo " IP: '${IP}'"
echo
# /etc/hosts
sed -i -E "/\s+${HOSTNAME}(\s|$)/d" /etc/hosts
echo -e "$IP\t${HOSTNAME} ${HOSTNAME%%.*}" >> /etc/hosts
echo "/etc/hosts:"
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
if grep -qE "^[^(#\s*)][0-9\.]+\s$HOSTNAME(\s|$)" /etc/hosts
then
sed -i.bak -e "s/.*${HOSTNAME}/${IP}\t${HOSTNAME}/" /etc/hosts
else
echo -e "$IP\t${HOSTNAME} ${HOSTNAME%%.*}" >> /etc/hosts
fi
cp -a /etc/resolv.conf /etc/resolv.conf.fixnet
cat > /etc/resolv.conf <<EOF
search ${DOMAIN}
search ${HOSTNAME#*.}
nameserver 127.0.0.1
EOF
echo "/etc/hosts:"
cat "/etc/hosts"
echo
echo "/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
class InventoryModule(BaseInventoryPlugin): # pylint: disable=R0901
class InventoryModule(BaseInventoryPlugin):
NAME = 'freeipa'

View File

@@ -33,7 +33,7 @@ __all__ = ["DEBUG_COMMAND_ALL", "DEBUG_COMMAND_LIST",
"paths", "tasks", "get_credentials_if_valid", "Encoding",
"DNSName", "getargspec", "certificate_loader",
"write_certificate_list", "boolean", "template_str",
"urlparse", "normalize_sshpubkey"]
"urlparse", "normalize_sshpubkey", "Email"]
DEBUG_COMMAND_ALL = 0b1111
# Print the while command list:
@@ -116,6 +116,7 @@ try:
from ipalib.krb_utils import get_credentials_if_valid
from ipapython.dnsutil import DNSName
from ipapython import kerberos
from ipapython.ipavalidate import Email
try:
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 [])))
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):
"""
Encode a certificate using base64.
@@ -1328,8 +1227,7 @@ class IPAAnsibleModule(AnsibleModule):
return module_params_get_with_type_cast(
self, name, datatype, allow_empty)
def params_fail_used_invalid(self, invalid_params, state, action=None,
params=None, param_mapping=None):
def params_fail_used_invalid(self, invalid_params, state, action=None):
"""
Fail module execution if one of the invalid parameters is not None.
@@ -1341,12 +1239,6 @@ class IPAAnsibleModule(AnsibleModule):
State being tested.
action:
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:
@@ -1355,15 +1247,8 @@ class IPAAnsibleModule(AnsibleModule):
msg = "Argument '{0}' can not be used with action "\
"'{2}' and state '{1}'"
_params = params if params is not None else self.params
for param in invalid_params:
if param_mapping 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:
if self.params.get(param) is not None:
self.fail_json(msg=msg.format(param, state, action))
def ipa_command(self, command, name, args):
@@ -1721,301 +1606,6 @@ class IPAAnsibleModule(AnsibleModule):
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):
ts = time.time()
# pylint: disable=super-with-arguments

View File

@@ -161,7 +161,7 @@ options:
type: list
elements: str
choices: ["password", "radius", "otp", "pkinit", "hardened", "idp",
"passkey", "disabled", ""]
"disabled", ""]
aliases: ["ipauserauthtype"]
ca_renewal_master_server:
description: Renewal master for IPA certificate authority.
@@ -344,7 +344,7 @@ config:
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):
@@ -426,7 +426,7 @@ def main():
user_auth_type=dict(type="list", elements="str", required=False,
choices=["password", "radius", "otp",
"pkinit", "hardened", "idp",
"passkey", "disabled", ""],
"disabled", ""],
aliases=["ipauserauthtype"]),
ca_renewal_master_server=dict(type="str", required=False),
domain_resolution_order=dict(type="list", elements="str",
@@ -515,6 +515,13 @@ def main():
msg="Argument '%s' must be between %d and %d."
% (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
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
for ipv in ['a', 'aaaa']:
record = '%srecord' % ipv
if (
record in args
and args.pop('%s_extra_create_reverse' % ipv, False)
):
if record in args and ('%s_extra_create_reverse' % ipv) in args:
cmds = create_reverse_ip_record(
module, zone_name, name, args[record])
_commands.extend(cmds)
del args['%s_extra_create_reverse' % ipv]
for record, fields in _RECORD_PARTS.items():
part_fields = [f for f in fields if f in args]
if part_fields:
@@ -1622,6 +1620,7 @@ def main():
commands.extend(cmds)
# Execute commands
changed = ansible_module.execute_ipa_commands(
commands, exception_handler=exception_handler)

View File

@@ -200,17 +200,6 @@ options:
required: false
type: list
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:
description: Work on group or member level
type: str
@@ -225,8 +214,7 @@ options:
description: State to ensure
type: str
default: present
choices: ["present", "absent", "renamed",
"query"]
choices: ["present", "absent", "renamed"]
author:
- Thomas Woerner (@t-woerner)
"""
@@ -334,39 +322,6 @@ EXAMPLES = """
ipaadmin_password: SomeADMINpassword
name: sysops,appops,ops, nongroup
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 = """
@@ -375,8 +330,8 @@ RETURN = """
from ansible.module_utils._text import to_text
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, \
gen_add_list, gen_intersection_list, gen_member_add_del_lists, \
api_check_param, convert_to_sid, ipalib_errors
gen_add_list, gen_intersection_list, api_check_param, \
convert_to_sid
from ansible.module_utils import six
if six.PY3:
unicode = str
@@ -393,67 +348,59 @@ else:
"deepcopy" in baseldap.LDAPObject.__json__.__code__.co_names
def group_show(module, name):
_args = {"all": True}
def find_group(module, name):
_args = {
"all": True,
"cn": name,
}
try:
_result = module.ipa_command("group_show", name, _args).get("result")
except ipalib_errors.NotFound:
return None
_result = module.ipa_command("group_find", name, _args)
# The returned services are of type ipapython.kerberos.Principal,
# also services are not case sensitive. Therefore services are
# converted to lowercase strings to be able to do the comparison.
if "member_service" in _result:
_result["member_service"] = \
[to_text(svc).lower() for svc in _result["member_service"]]
# user_find is returning SIDs, but user_show is not. Therefore convert
# external users to SIDs.
if "ipaexternalmember" in _result:
_result["ipaexternalmember"] = \
convert_to_sid(_result["ipaexternalmember"])
return _result
if len(_result["result"]) > 1:
module.fail_json(
msg="There is more than one group '%s'" % (name))
elif len(_result["result"]) == 1:
_res = _result["result"][0]
# The returned services are of type ipapython.kerberos.Principal,
# also services are not case sensitive. Therefore services are
# converted to lowercase strings to be able to do the comparison.
if "member_service" in _res:
_res["member_service"] = \
[to_text(svc).lower() for svc in _res["member_service"]]
return _res
return None
def query_convert_result(module, res):
_res = {}
for key in res:
try:
if key.startswith("member_") or key.startswith("membermanager_"):
_res[key] = [to_text(svc) for svc in res[key]]
elif isinstance(res[key], (list, tuple)):
if len(res[key]) == 1:
_res[key] = to_text(res[key][0])
else:
_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 gen_args(description, gid, nomembers):
_args = {}
if description is not None:
_args["description"] = description
if gid is not None:
_args["gidnumber"] = gid
if nomembers is not None:
_args["nomembers"] = nomembers
return _args
def group_find(module, name):
_args = {"all": True}
def gen_member_args(user, group, service, externalmember, idoverrideuser):
_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:
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
return _args
def check_parameters(module, state, action, group_params):
def check_parameters(module, state, action):
invalid = ["description", "gid", "posix", "nonposix", "external",
"nomembers"]
if action == "group":
@@ -468,14 +415,7 @@ def check_parameters(module, state, action, group_params):
invalid.extend(["user", "group", "service", "externalmember"])
else:
invalid.append("rename")
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)
module.params_fail_used_invalid(invalid, state, action)
def is_external_group(res_find):
@@ -505,75 +445,6 @@ def check_objectclass_args(module, res_find, posix, external):
"`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():
group_spec = dict(
# present
@@ -604,11 +475,6 @@ def main():
rename=dict(type="str", required=False, default=None,
aliases=["new_name"]),
)
query_param_settings = IPAAnsibleModule.build_query_param_settings(
PARAM_MAPPING, QUERY_FIELDS
)
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# general
@@ -625,16 +491,11 @@ def main():
),
elements='dict',
required=False),
# query
query_param=dict(type="list", elements="str", default=None,
choices=["ALL", "BASE", "PKEY_ONLY"]
+ query_param_settings["ALL"],
required=False),
# general
action=dict(type="str", default="group",
choices=["member", "group"]),
state=dict(type="str", default="present",
choices=["present", "absent", "renamed", "query"]),
choices=["present", "absent", "renamed"]),
# Add group specific parameters for simple use case
**group_spec
@@ -643,6 +504,7 @@ def main():
# same time
mutually_exclusive=[['posix', 'nonposix', 'external'],
["name", "groups"]],
required_one_of=[["name", "groups"]],
supports_check_mode=True,
)
@@ -654,27 +516,34 @@ def main():
names = ansible_module.params_get("name")
groups = ansible_module.params_get("groups")
# query
query_param = ansible_module.params_get("query_param")
# present
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
action = ansible_module.params_get("action")
state = ansible_module.params_get("state")
# Check parameters
if state != "query":
if (names is None or len(names) < 1) and \
(groups is None or len(groups) < 1):
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 (names is None or len(names) < 1) and \
(groups is None or len(groups) < 1):
ansible_module.fail_json(msg="At least one name or groups is required")
if state in ["present", "renamed"]:
if names is not None and len(names) != 1:
@@ -682,6 +551,12 @@ def main():
ansible_module.fail_json(
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
# and posix require to have a fix in IPA:
#
@@ -705,6 +580,15 @@ def main():
"supported by your IPA version: "
"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
if groups is not None:
names = groups
@@ -714,24 +598,35 @@ def main():
changed = False
exit_args = {}
# If nonposix is used, set posix as not nonposix
if nonposix is not None:
posix = not nonposix
# Connect to IPA API
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(
"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(
"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(
"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 = []
group_set = set()
@@ -743,9 +638,42 @@ def main():
ansible_module.fail_json(
msg="group '%s' is used more than once" % 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(
group_name, PARAM_MAPPING)
check_parameters(ansible_module, state, action)
elif (
isinstance(
@@ -753,54 +681,17 @@ def main():
)
):
name = group_name
group_params = IPAAnsibleModule.extract_params(
ansible_module, PARAM_MAPPING)
else:
ansible_module.fail_json(msg="Group '%s' is not valid" %
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
res_find = group_show(ansible_module, name)
res_find = find_group(ansible_module, name)
# external members must be handled as SID
externalmember = convert_to_sid(
group_params.get("externalmember"))
# external members must de handled as SID
externalmember = convert_to_sid(externalmember)
# idoverrides need to be compared through SID
idoverrideuser = group_params.get("idoverrideuser")
idoverrideuser_sid = convert_to_sid(idoverrideuser)
res_idoverrideuser_sid = convert_to_sid(
(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 = [], []
idoverrides_add, idoverrides_del = [], []
membermanager_user_add, membermanager_user_del = [], []
membermanager_group_add, membermanager_group_del = [], []
# Create command
if state == "present":
@@ -824,8 +720,7 @@ def main():
external)
# Generate args
args = IPAAnsibleModule.gen_args_from_mapping(
PARAM_MAPPING, group_params)
args = gen_args(description, gid, nomembers)
if action == "group":
# Found the group
@@ -864,10 +759,89 @@ def main():
classes.append("posixgroup")
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":
if res_find is None:
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":
if action == "group":
if res_find is not None:
@@ -877,6 +851,36 @@ def main():
if res_find is None:
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":
if res_find is None:
ansible_module.fail_json(msg="No group '%s'" % name)
@@ -885,71 +889,6 @@ def main():
else:
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
# setup member args for add/remove members.
add_member_args = {

View File

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

View File

@@ -281,14 +281,6 @@ def main():
# Connect to IPA API
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 = []
for name in names:
@@ -329,18 +321,6 @@ def main():
del args["iparangetype"]
commands.append([name, "idrange_mod", args])
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])
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
elements: str
required: false
sysaccount:
description: List of sysaccounts.
type: list
elements: str
required: false
action:
description: Work on role or member level.
type: str
@@ -182,7 +177,7 @@ def check_parameters(module):
"description",
"user", "group",
"host", "hostgroup",
"service", "sysaccount",
"service",
"privilege",
]
@@ -230,7 +225,7 @@ def ensure_absent_state(module, name, action, res_find):
{"privilege": del_list}])
member_args = {}
for key in ['user', 'group', 'hostgroup', 'sysaccount']:
for key in ['user', 'group', 'hostgroup']:
_members = module.params_get_lowercase(key)
if _members:
del_list = gen_intersection_list(
@@ -340,7 +335,7 @@ def ensure_role_with_members_is_present(module, name, res_find, action):
add_members = {}
del_members = {}
for key in ["user", "group", "hostgroup", "sysaccount"]:
for key in ["user", "group", "hostgroup"]:
_members = module.params_get_lowercase(key)
if _members is not None:
add_list, del_list = gen_add_del_lists(
@@ -442,8 +437,6 @@ def create_module():
default=None),
service=dict(required=False, type='list', elements="str",
default=None),
sysaccount=dict(required=False, type='list', elements="str",
default=None),
# state
action=dict(type="str", default="role",
@@ -474,15 +467,8 @@ def main():
state = ansible_module.params_get("state")
action = ansible_module.params_get("action")
names = ansible_module.params_get("name")
sysaccount = ansible_module.params_get("sysaccount")
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:
cmds = role_commands_for_name(ansible_module, state, action, name)
commands.extend(cmds)

View File

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

View File

@@ -51,7 +51,8 @@ options:
suboptions:
name:
description: The sudorule name
type: str
type: list
elements: str
required: true
aliases: ["cn"]
description:
@@ -461,7 +462,7 @@ def init_ansible_module():
required=False),
sudorules=dict(
type="list",
default=None,
defalut=None,
options=dict(
# name of the sudorule
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=
source_order: mark test as order bound
playbook: playbook tests
pythonpath = tests

View File

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

View File

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

View File

@@ -1 +0,0 @@
setuptools

View File

@@ -91,21 +91,20 @@
enabled: yes
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
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
ansible.builtin.shell: >
firewall-cmd
--permanent
--info-zone="{{ ipabackup_firewalld_zone }}"
>/dev/null
- name: Firewalld - Verify permanent zone "{{ ipabackup_firewalld_zone }}"
ansible.builtin.shell: >
firewall-cmd
--permanent
--info-zone="{{ ipabackup_firewalld_zone }}"
>/dev/null
when: ipabackup_firewalld_zone is defined
### 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_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_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

View File

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

View File

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

View File

@@ -91,11 +91,6 @@ options:
changes
type: bool
required: no
dns_over_tls:
description: Configure DNS over TLS
type: bool
default: no
required: no
preserve_sssd:
description: Preserve old SSSD configuration if possible
type: bool
@@ -145,7 +140,6 @@ def main():
fixed_primary=dict(required=False, type='bool'),
permit=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'),
no_krb5_offline_passwords=dict(required=False, type='bool'),
),
@@ -175,13 +169,11 @@ def main():
options.primary = module.params.get('fixed_primary')
options.permit = module.params.get('permit')
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.no_krb5_offline_passwords = module.params.get(
'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)
client_domain = hostname[hostname.find(".") + 1:]

View File

@@ -124,16 +124,6 @@ options:
type: bool
required: 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:
description:
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,
validate_hostname, nssldap_exists, gssapi, remove_file,
check_ip_addresses, ipadiscovery, print_port_conf_info,
IPA_PYTHON_VERSION, getargspec, services,
CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION
IPA_PYTHON_VERSION, getargspec
)
@@ -339,9 +328,6 @@ def main():
default=None),
all_ip_addresses=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
enable_dns_updates=dict(required=False, type='bool',
default=False),
@@ -370,8 +356,6 @@ def main():
options.ip_addresses = module.params.get('ip_addresses')
options.all_ip_addresses = module.params.get('all_ip_addresses')
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')
# 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.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:
self = options
@@ -460,10 +434,9 @@ def main():
# ClientInstall
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:
if not isinstance(value, list):
raise ValueError("Expected list, got {0!r}".format(value))
# this is what init() does
value = value[-1]
if not os.path.exists(value):

View File

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

View File

@@ -1,23 +1,11 @@
---
# 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
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
ansible.builtin.set_fact:
@@ -50,7 +38,7 @@
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
- name: Install - Fail on missing ipaclient_dns_servers
- name: Install - Fail on missing ipaclient_servers
ansible.builtin.fail:
msg: "ipaclient_dns_servers is required for ipaclient_configure_dns_resolver"
when: ipaclient_dns_servers is not defined
@@ -81,10 +69,9 @@
ip_addresses: "{{ ipaclient_ip_addresses | default(omit) }}"
all_ip_addresses: "{{ ipaclient_all_ip_addresses }}"
on_master: "{{ ipaclient_on_master }}"
dns_over_tls: "{{ ipaclient_dns_over_tls }}"
no_dnssec_validation: "{{ ipaclient_no_dnssec_validation }}"
### sssd ###
enable_dns_updates: "{{ ipasssd_enable_dns_updates }}"
enable_dns_updates: "{{ ipassd_enable_dns_updates
| default(ipasssd_enable_dns_updates) }}"
register: result_ipaclient_test
- name: Install - Client deployment
@@ -181,10 +168,10 @@
- name: Install - Store the previously obtained OTP
no_log: yes
when: result_ipaclient_get_otp.host is defined
ansible.builtin.set_fact:
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:
- name: Install - Report error for OTP generation
ansible.builtin.debug:
@@ -334,12 +321,16 @@
no_sshd: "{{ ipaclient_no_sshd }}"
no_sudo: "{{ ipaclient_no_sudo }}"
all_ip_addresses: "{{ ipaclient_all_ip_addresses }}"
fixed_primary: "{{ ipasssd_fixed_primary }}"
permit: "{{ ipasssd_permit }}"
enable_dns_updates: "{{ ipasssd_enable_dns_updates }}"
dns_over_tls: "{{ ipaclient_dns_over_tls }}"
preserve_sssd: "{{ ipasssd_preserve_sssd }}"
no_krb5_offline_passwords: "{{ ipasssd_no_krb5_offline_passwords }}"
fixed_primary: "{{ ipassd_fixed_primary
| default(ipasssd_fixed_primary) }}"
permit: "{{ ipassd_permit | default(ipasssd_permit) }}"
enable_dns_updates: "{{ ipassd_enable_dns_updates
| default(ipasssd_enable_dns_updates) }}"
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
ipaclient_api:
@@ -374,20 +365,23 @@
ca_enabled: "{{ result_ipaclient_api.ca_enabled }}"
on_master: "{{ ipaclient_on_master }}"
dnsok: "{{ result_ipaclient_test.dnsok }}"
enable_dns_updates: "{{ ipasssd_enable_dns_updates }}"
dns_over_tls: "{{ ipaclient_dns_over_tls }}"
no_dnssec_validation: "{{ ipaclient_no_dnssec_validation }}"
enable_dns_updates: "{{ ipassd_enable_dns_updates
| default(ipasssd_enable_dns_updates) }}"
all_ip_addresses: "{{ ipaclient_all_ip_addresses }}"
ip_addresses: "{{ ipaclient_ip_addresses | default(omit) }}"
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_sshd: "{{ ipaclient_no_sshd }}"
no_sudo: "{{ ipaclient_no_sudo }}"
subid: "{{ ipaclient_subid }}"
fixed_primary: "{{ ipasssd_fixed_primary }}"
permit: "{{ ipasssd_permit }}"
no_krb5_offline_passwords: "{{ ipasssd_no_krb5_offline_passwords }}"
fixed_primary: "{{ ipassd_fixed_primary
| default(ipasssd_fixed_primary) }}"
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 }}"
nosssd_files: "{{ result_ipaclient_test.nosssd_files }}"
selinux_works: "{{ result_ipaclient_test.selinux_works }}"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,4 +2,3 @@
# vars/default.yml
---
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_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_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
------------------

View File

@@ -224,32 +224,6 @@ options:
type: bool
default: 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:
description: Enable support for trusted domains for old clients
type: bool
@@ -380,15 +354,6 @@ def main():
choices=['first', 'only'], default=None),
no_dnssec_validation=dict(required=False, type='bool',
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
enable_compat=dict(required=False, type='bool', default=False),
netbios_name=dict(required=False, type='str'),
@@ -465,11 +430,6 @@ def main():
options.forward_policy = ansible_module.params.get('forward_policy')
options.no_dnssec_validation = ansible_module.params.get(
'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
options.enable_compat = ansible_module.params.get('enable_compat')
options.netbios_name = ansible_module.params.get('netbios_name')

View File

@@ -72,32 +72,6 @@ options:
type: bool
default: 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:
description: The dns ip_addresses setting
type: list
@@ -143,9 +117,6 @@ from ansible.module_utils.ansible_ipa_replica import (
gen_ReplicaConfig, gen_remote_api, api, redirect_stdout, dns,
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():
@@ -164,14 +135,6 @@ def main():
choices=['first', 'only'], default=None),
no_dnssec_validation=dict(required=False, type='bool',
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
dns_ip_addresses=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.no_dnssec_validation = ansible_module.params.get(
'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
dns.ip_addresses = ansible_module_get_parsed_ip_addresses(
ansible_module, 'dns_ip_addresses')

View File

@@ -181,32 +181,6 @@ options:
type: bool
default: 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:
- 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,
redirect_stdout, create_ipa_conf, ipautil,
x509, validate_domain_name, common_check,
IPA_PYTHON_VERSION, getargspec, adtrustinstance, install_ca_cert,
services, CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION
IPA_PYTHON_VERSION, getargspec, adtrustinstance, install_ca_cert
)
@@ -277,14 +250,6 @@ def main():
choices=['first', 'only'], default=None),
no_dnssec_validation=dict(required=False, type='bool',
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.no_dnssec_validation = ansible_module.params.get(
'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 ###########################################################
@@ -459,14 +419,6 @@ def main():
ansible_module.fail_json(
msg="You cannot specify a --no-dnssec-validation 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:
ansible_module.fail_json(
msg="You cannot specify a --forwarder option together with "
@@ -483,31 +435,6 @@ def main():
ansible_module.fail_json(
msg="You cannot specify a --auto-reverse option together with "
"--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
if installer.servers and not installer.domain_name:
@@ -522,10 +449,6 @@ def main():
ansible_module.fail_json(
msg="You must specify at least one of --forwarder, "
"--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 \
not os.path.exists(installer.dirsrv_config_file):
@@ -563,11 +486,6 @@ def main():
if installer.domain_name is not None:
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 #########################################
##########################################################################

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,7 @@
# defaults file for ipareplica
# vars/default.yml
---
ipareplica_packages: [ "ipa-server", "python3-libselinux" ]
ipareplica_packages_dns: [ "ipa-server-dns" ]
ipareplica_packages_dot: [ "ipa-server-encrypted-dns" ]
ipareplica_packages_adtrust: [ "ipa-server-trust-ad" ]
ipareplica_packages: [ "freeipa-server", "python3-libselinux" ]
ipareplica_packages_dns: [ "freeipa-server-dns" ]
ipareplica_packages_adtrust: [ "freeipa-server-trust-ad" ]
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_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_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
------------------

View File

@@ -174,32 +174,6 @@ options:
type: bool
default: 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:
description: Enable support for trusted domains for old clients
type: bool
@@ -306,15 +280,6 @@ def main():
choices=['first', 'only'], default=None),
no_dnssec_validation=dict(required=False, type='bool',
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
enable_compat=dict(required=False, type='bool', default=False),
netbios_name=dict(required=False, type='str'),
@@ -395,11 +360,6 @@ def main():
options.forward_policy = ansible_module.params.get('forward_policy')
options.no_dnssec_validation = ansible_module.params.get(
'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
options.enable_compat = ansible_module.params.get('enable_compat')
options.netbios_name = ansible_module.params.get('netbios_name')

View File

@@ -83,32 +83,6 @@ options:
type: bool
default: 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:
description: The dns ip_addresses setting
type: list
@@ -133,13 +107,9 @@ from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_server import (
check_imports, AnsibleModuleLog, setup_logging, options, paths, dns,
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():
ansible_module = AnsibleModule(
@@ -160,14 +130,6 @@ def main():
default='first'),
no_dnssec_validation=dict(required=False, type='bool',
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
dns_ip_addresses=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.no_dnssec_validation = ansible_module.params.get(
'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
dns.ip_addresses = ansible_module_get_parsed_ip_addresses(
ansible_module, 'dns_ip_addresses')
@@ -208,16 +165,25 @@ def main():
# init ##################################################################
# pylint: disable=unused-variable
fstore = sysrestore.FileStore(paths.SYSRESTORE) # noqa: F841
# pylint: enable=unused-variable
fstore = sysrestore.FileStore(paths.SYSRESTORE)
api_Backend_ldap2(options.host_name, options.setup_ca, connect=True)
# setup dns #############################################################
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 ##################################################################

View File

@@ -265,32 +265,6 @@ options:
type: bool
default: 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:
description: Enable support for trusted domains for old clients
type: bool
@@ -338,8 +312,7 @@ from ansible.module_utils.ansible_ipa_server import (
check_dirsrv, ScriptError, get_fqdn, verify_fqdn, BadHostError,
validate_domain_name, load_pkcs12, IPA_PYTHON_VERSION,
encode_certificate, check_available_memory, getargspec, adtrustinstance,
get_min_idstart, SerialNumber, services, service,
CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION
get_min_idstart, SerialNumber
)
from ansible.module_utils import six
@@ -423,14 +396,6 @@ def main():
choices=['first', 'only'], default=None),
no_dnssec_validation=dict(required=False, type='bool',
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
enable_compat=dict(required=False, type='bool', default=False),
netbios_name=dict(required=False, type='str'),
@@ -517,11 +482,6 @@ def main():
options.forward_policy = ansible_module.params.get('forward_policy')
options.no_dnssec_validation = ansible_module.params.get(
'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
options.enable_compat = ansible_module.params.get('enable_compat')
options.netbios_name = ansible_module.params.get('netbios_name')
@@ -643,14 +603,6 @@ def main():
raise RuntimeError(
"You cannot specify a --no-dnssec-validation 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:
raise RuntimeError(
"You cannot specify a --forwarder option together with "
@@ -667,31 +619,7 @@ def main():
raise RuntimeError(
"You cannot specify a --auto-reverse option together with "
"--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 self.add_agents:
raise RuntimeError(
@@ -749,10 +677,6 @@ def main():
raise RuntimeError(
"You must specify at least one of --forwarder, "
"--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(
[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.
cert_file_req = (options.dirsrv_cert_files, options.http_cert_files)
cert_file_opt = (options.pkinit_cert_files,)
@@ -1297,7 +1208,6 @@ def main():
domainlevel=options.domainlevel,
sid_generation_always=sid_generation_always,
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",
"paths", "api", "ipautil", "adtrust_imported", "NUM_VERSION",
"time_service", "kra_imported", "dsinstance", "IPA_PYTHON_VERSION",
"NUM_VERSION", "SerialNumber", "realm_to_ldapi_uri",
"CLIENT_SUPPORTS_NO_DNSSEC_VALIDATION"]
"NUM_VERSION", "SerialNumber", "realm_to_ldapi_uri"]
import sys
import logging
@@ -75,7 +74,6 @@ except ImportError:
return ArgSpec(args, varargs, varkw, defaults)
ANSIBLE_IPA_SERVER_MODULE_IMPORT_ERROR = None # pylint: disable=invalid-name
try:
from contextlib import contextmanager as contextlib_contextmanager
from ansible.module_utils import six
@@ -218,29 +216,17 @@ try:
except ImportError:
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:
# IPA version < 4.5
raise RuntimeError("freeipa version '%s' is too old" % VERSION)
except ImportError as _err:
# pylint: disable=invalid-name
ANSIBLE_IPA_SERVER_MODULE_IMPORT_ERROR = str(_err)
# pylint: enable=invalid-name
for attr in __all__:
setattr(sys.modules[__name__], attr, None)
else:
ANSIBLE_IPA_SERVER_MODULE_IMPORT_ERROR = None
logger = logging.getLogger("ipa-server-install")
@@ -255,13 +241,11 @@ def setup_logging():
@contextlib_contextmanager
def redirect_stdout(stream):
old_stdout = sys.stdout
sys.stdout = stream
try:
yield stream
finally:
sys.stdout = old_stdout
sys.stdout = sys.__stdout__
class AnsibleModuleLog():

View File

@@ -1,42 +1,32 @@
---
# 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
when: ipaserver_install_packages | bool
block:
- name: Install - Ensure that IPA server packages are installed
ansible.builtin.package:
name: "{{ ipaserver_packages }}"
state: present
- name: Install - Set packages for installation
ansible.builtin.set_fact:
_ipapackages: "{{ ipaserver_packages }}"
- name: Install - Set packages for installlation, add DNS
ansible.builtin.set_fact:
_ipapackages: "{{ _ipapackages + ipaserver_packages_dns }}"
- name: Install - Ensure that IPA server packages for dns are installed
ansible.builtin.package:
name: "{{ ipaserver_packages_dns }}"
state: present
when: ipaserver_setup_dns | bool
- name: Install - Set packages for installlation, add DOT
ansible.builtin.set_fact:
_ipapackages: "{{ _ipapackages + ipaserver_packages_dot }}"
when: ipaserver__dns_over_tls | bool
- name: Install - Set packages for installlation, add adtrust
ansible.builtin.set_fact:
_ipapackages: "{{ _ipapackages + ipaserver_packages_adtrust }}"
- name: Install - Ensure that IPA server packages for adtrust are installed
ansible.builtin.package:
name: "{{ ipaserver_packages_adtrust }}"
state: present
when: ipaserver_setup_adtrust | bool
- name: Install - Set packages for installlation, add firewalld
ansible.builtin.set_fact:
_ipapackages: "{{ _ipapackages + ipaserver_packages_firewalld }}"
- name: Install - Ensure that firewall packages installed
ansible.builtin.package:
name: "{{ ipaserver_packages_firewalld }}"
state: present
when: ipaserver_setup_firewalld | bool
- name: Install - Ensure that packages are installed
ansible.builtin.package:
name: "{{ _ipapackages }}"
state: present
- name: Install - Firewall configuration
when: ipaserver_setup_firewalld | bool
@@ -47,21 +37,20 @@
enabled: yes
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
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
ansible.builtin.shell: >
firewall-cmd
--permanent
--info-zone="{{ ipaserver_firewalld_zone }}"
>/dev/null
- name: Firewalld - Verify permanent zone "{{ ipaserver_firewalld_zone }}"
ansible.builtin.shell: >
firewall-cmd
--permanent
--info-zone="{{ ipaserver_firewalld_zone }}"
>/dev/null
when: ipaserver_firewalld_zone is defined
- name: Copy external certs
ansible.builtin.include_tasks: "{{ role_path }}/tasks/copy_external_cert.yml"
@@ -132,11 +121,6 @@
auto_forwarders: "{{ ipaserver_auto_forwarders }}"
forward_policy: "{{ ipaserver_forward_policy | default(omit) }}"
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 ###
enable_compat: "{{ ipaserver_enable_compat }}"
netbios_name: "{{ ipaserver_netbios_name | default(omit) }}"
@@ -208,11 +192,6 @@
auto_forwarders: "{{ ipaserver_auto_forwarders }}"
forward_policy: "{{ ipaserver_forward_policy | default(omit) }}"
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 ###
enable_compat: "{{ ipaserver_enable_compat }}"
netbios_name: "{{ ipaserver_netbios_name | default(omit) }}"
@@ -402,11 +381,6 @@
forward_policy: "{{ result_ipaserver_prepare.forward_policy }}"
zonemgr: "{{ ipaserver_zonemgr | default(omit) }}"
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 ###
dns_ip_addresses: "{{ result_ipaserver_prepare.dns_ip_addresses }}"
dns_reverse_zones: "{{ result_ipaserver_prepare.dns_reverse_zones }}"
@@ -458,7 +432,6 @@
ipaclient_no_ntp:
"{{ 'true' if result_ipaserver_test.ipa_python_version >= 40690
else 'false' }}"
ipaclient_dns_over_tls: "{{ result_ipaserver_test.client_dns_over_tls }}"
ipaclient_install_packages: no
- name: Install - Enable IPA
@@ -479,8 +452,6 @@
{{ "--add-service=freeipa-trust" if ipaserver_setup_adtrust | 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 "" }}
when: ipaserver_setup_firewalld | bool
@@ -494,8 +465,6 @@
{{ "--add-service=freeipa-trust" if ipaserver_setup_adtrust | 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 "" }}
when: ipaserver_setup_firewalld | bool

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -61,12 +61,7 @@ good-names =
dt, ca,
# These are utils tools, and not part of the released collection.
galaxyfy-playbook, galaxyfy-README, galaxyfy-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
module_EXAMPLES
[pylint.IMPORTS]
@@ -89,7 +84,6 @@ ignored-modules =
[pylint.DESIGN]
max-attributes=12
max-public-methods=25
[pylint.REFACTORING]
max-nested-blocks = 9

View File

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

View File

@@ -124,7 +124,7 @@
reason: 9
state: revoked
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
ipacert:
@@ -140,7 +140,7 @@
certificate_out: "/root/cert_1.pem"
state: requested
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
ansible.builtin.file:
@@ -155,7 +155,7 @@
certificate_out: "/root/retrieved.pem"
state: retrieved
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
ansible.builtin.file:
@@ -194,7 +194,7 @@
profile: invalid_profile
state: requested
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

View File

@@ -136,7 +136,7 @@
reason: 9
state: revoked
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
ipacert:
@@ -153,7 +153,7 @@
certificate_out: "/root/cert_1.pem"
state: requested
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
ansible.builtin.file:
@@ -168,7 +168,7 @@
certificate_out: "/root/retrieved.pem"
state: retrieved
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
ansible.builtin.file:
@@ -207,7 +207,7 @@
profile: invalid_profile
state: requested
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

View File

@@ -123,7 +123,7 @@
reason: 9
state: revoked
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
ipacert:
@@ -140,7 +140,7 @@
certificate_out: "/root/cert_1.pem"
state: requested
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
ansible.builtin.file:
@@ -155,7 +155,7 @@
certificate_out: "/root/retrieved.pem"
state: retrieved
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
ansible.builtin.file:
@@ -194,7 +194,7 @@
profile: invalid_profile
state: requested
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

View File

@@ -34,6 +34,16 @@
ipaapi_context: "{{ ipa_context | default(omit) }}"
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'
ipaconfig:
ipaadmin_password: SomeADMINpassword
@@ -400,7 +410,7 @@
searchrecordslimit: '{{ previousconfig.config.searchrecordslimit | default(100) | int }}'
usersearch: '{{ previousconfig.config.usersearch | 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) }}'
userobjectclasses: '{{ previousconfig.config.userobjectclasses | default(omit) }}'
pwdexpnotify: '{{ previousconfig.config.pwdexpnotify | default(4) | int }}'
@@ -436,7 +446,7 @@
searchrecordslimit: '{{ previousconfig.config.searchrecordslimit | default(omit) | int }}'
usersearch: '{{ previousconfig.config.usersearch | 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) }}'
userobjectclasses: '{{ previousconfig.config.userobjectclasses | default(omit) }}'
pwdexpnotify: '{{ previousconfig.config.pwdexpnotify | default(omit) | int }}'
@@ -473,7 +483,7 @@
searchrecordslimit: '{{ previousconfig.config.searchrecordslimit | default(omit) | int }}'
usersearch: '{{ previousconfig.config.usersearch | 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) }}'
userobjectclasses: '{{ previousconfig.config.userobjectclasses | default(omit) }}'
pwdexpnotify: '{{ previousconfig.config.pwdexpnotify | default(omit) | int }}'

View File

@@ -5,8 +5,6 @@
gather_facts: no
tasks:
- name: Include tasks ../env_freeipa_facts.yml
ansible.builtin.include_tasks: ../env_freeipa_facts.yml
# GET CURRENT CONFIG
@@ -82,36 +80,6 @@
register: result
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
ipaconfig:
ipaadmin_password: SomeADMINpassword
@@ -170,6 +138,6 @@
ipaconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
pac_type: '{{ previousconfig.config.pac_type | default("") }}'
user_auth_type: '{{ previousconfig.config.user_auth_type | default("") }}'
configstring: '{{ previousconfig.config.configstring | default("") }}'
pac_type: '{{ previousconfig.config.pac_type }}'
user_auth_type: '{{ previousconfig.config.user_auth_type }}'
configstring: '{{ previousconfig.config.configstring }}'

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