mirror of
https://github.com/freeipa/ansible-freeipa.git
synced 2026-03-28 14:23:06 +00:00
Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16a4eb81ce | ||
|
|
cd16490531 | ||
|
|
7b6bc32fa0 | ||
|
|
6b3fb78db6 | ||
|
|
67df9e83c7 | ||
|
|
14be339af0 | ||
|
|
76251ead2c | ||
|
|
74028bd36c | ||
|
|
43217b9e70 | ||
|
|
96209f6945 | ||
|
|
7eac30127a | ||
|
|
719d1cd056 | ||
|
|
832d44d986 | ||
|
|
82f403c0de | ||
|
|
fa4a90e628 | ||
|
|
c38ff9b78c | ||
|
|
85b1c54ce1 | ||
|
|
6d5f3f3274 | ||
|
|
1dba4ba408 | ||
|
|
e867373fc0 | ||
|
|
c5c8cb3b04 | ||
|
|
8944999657 | ||
|
|
b7a04bc49b | ||
|
|
935bef4b9f | ||
|
|
8e139e2fe9 | ||
|
|
332d41dc46 | ||
|
|
ab94ff07a0 | ||
|
|
5a5b3c1655 | ||
|
|
74663b877a | ||
|
|
2f06f194f1 | ||
|
|
3148c10480 | ||
|
|
f4187a1453 | ||
|
|
7126dec0f3 | ||
|
|
3d241e55b4 | ||
|
|
173acf282b | ||
|
|
39ba225784 | ||
|
|
b7ccd8fed5 | ||
|
|
ef94b703df | ||
|
|
0dc58be3f6 | ||
|
|
b64da1dbb7 | ||
|
|
84b5d33c62 | ||
|
|
5ac7143f42 | ||
|
|
07d91e02d1 | ||
|
|
127d758100 | ||
|
|
4ff6e35c28 | ||
|
|
a1230cabc6 | ||
|
|
411f5f3467 | ||
|
|
8779384614 | ||
|
|
2cc1484ad7 | ||
|
|
77c1d206d3 | ||
|
|
52241fe233 | ||
|
|
f53ca3ad39 | ||
|
|
60905ef5bf | ||
|
|
0d48da060d | ||
|
|
5cdbcf6442 | ||
|
|
08b0fc02ba | ||
|
|
6cec03eb15 | ||
|
|
65a1fd7804 | ||
|
|
bcb6a68230 | ||
|
|
8f8a16f815 | ||
|
|
bfcc62a27f | ||
|
|
8ba32bfc26 | ||
|
|
69306a6177 | ||
|
|
967a2d8e56 | ||
|
|
2626715db6 | ||
|
|
2166a9f7a2 | ||
|
|
8b4bb631a5 | ||
|
|
f17f83d6bd | ||
|
|
a3517a3a23 | ||
|
|
5aa1c7cb57 | ||
|
|
15e9201dab | ||
|
|
6caa58e8be | ||
|
|
5c61f14cc1 | ||
|
|
b3a74e616a | ||
|
|
cbff802d13 | ||
|
|
4ceb6aa05d | ||
|
|
35614d7a88 | ||
|
|
7a9ea832a1 | ||
|
|
2804ec3f83 | ||
|
|
bef748cfdc | ||
|
|
c24e8b498e | ||
|
|
fe16df8a6c | ||
|
|
d804dc470e | ||
|
|
8fa3daece8 | ||
|
|
0cad1fa879 | ||
|
|
780e6b1436 | ||
|
|
216a5d4f9d | ||
|
|
f8ff833b03 | ||
|
|
b92da82661 | ||
|
|
ce05b5e137 | ||
|
|
a826bf1781 | ||
|
|
a3a6919416 | ||
|
|
e9c6e93608 | ||
|
|
f40f4d4c9a | ||
|
|
7b7d9c9957 | ||
|
|
c0c3394d8d | ||
|
|
11205102af | ||
|
|
22401d18d6 | ||
|
|
9b5a54c4fa | ||
|
|
9920a76777 | ||
|
|
249eab6047 | ||
|
|
29f046b8e2 | ||
|
|
2317c20556 | ||
|
|
0d1f8b53b8 | ||
|
|
0a468d32e8 | ||
|
|
03c65bd761 | ||
|
|
b87b346a0a | ||
|
|
e92db5c5cd | ||
|
|
1028f61b6c | ||
|
|
1fde1764af | ||
|
|
4321478cf0 | ||
|
|
900c76e810 | ||
|
|
1ecdbd3a49 | ||
|
|
47a1d50c84 | ||
|
|
3fe41a5260 | ||
|
|
3a304e8bd7 | ||
|
|
86e089fd42 | ||
|
|
3eb86b2c2d | ||
|
|
3bd68ac0fa | ||
|
|
0f2c37612e | ||
|
|
4e831b0cb8 | ||
|
|
34973c04c6 | ||
|
|
bc694b722c | ||
|
|
92d579be41 | ||
|
|
e55a41ca0c | ||
|
|
0f7ebd22fd | ||
|
|
f4c9e28715 | ||
|
|
81e6cbe6b7 | ||
|
|
9ecbe2315e | ||
|
|
102d6c5a6d | ||
|
|
66bbc50c4d | ||
|
|
a38106afae | ||
|
|
47940b48c6 | ||
|
|
8114120814 | ||
|
|
505cb356c1 | ||
|
|
d2e0cad90b | ||
|
|
9c735939a2 | ||
|
|
22214dafff | ||
|
|
2c9ee7d842 | ||
|
|
de3c6c0ace | ||
|
|
ff084fbd96 | ||
|
|
ca5496918a | ||
|
|
48c0fd0a28 | ||
|
|
f2a1d50b82 | ||
|
|
b22bf4dfb9 | ||
|
|
f012da22ce |
2
.github/workflows/ansible-test.yml
vendored
2
.github/workflows/ansible-test.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
name: Verify ansible-test sanity
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Run ansible-test
|
||||
|
||||
24
.github/workflows/docs.yml
vendored
24
.github/workflows/docs.yml
vendored
@@ -8,10 +8,10 @@ jobs:
|
||||
name: Check Ansible Documentation with ansible-core 2.13.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v4.3.0
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install Ansible 2.13
|
||||
@@ -25,10 +25,10 @@ jobs:
|
||||
name: Check Ansible Documentation with ansible-core 2.14.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v4.3.0
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install Ansible 2.14
|
||||
@@ -42,10 +42,10 @@ jobs:
|
||||
name: Check Ansible Documentation with ansible-core 2.15.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v4.3.0
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install Ansible 2.15
|
||||
@@ -59,10 +59,10 @@ jobs:
|
||||
name: Check Ansible Documentation with latest Ansible version.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v4.3.0
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install Ansible-latest
|
||||
|
||||
34
.github/workflows/lint.yml
vendored
34
.github/workflows/lint.yml
vendored
@@ -8,15 +8,15 @@ jobs:
|
||||
name: Verify ansible-lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v4.3.0
|
||||
- uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Run ansible-lint
|
||||
run: |
|
||||
pip install "ansible-core>=2.16,<2.17" 'ansible-lint>=6.21'
|
||||
pip install "ansible-core>=2.16,<2.17" 'ansible-lint==6.22'
|
||||
utils/build-galaxy-release.sh -ki
|
||||
cd .galaxy-build
|
||||
ansible-lint --profile production --exclude tests/integration/ --exclude tests/unit/ --parseable --nocolor
|
||||
@@ -25,10 +25,10 @@ jobs:
|
||||
name: Verify yamllint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v4.3.0
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Run yaml-lint
|
||||
@@ -38,10 +38,10 @@ jobs:
|
||||
name: Verify pydocstyle
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v4.3.0
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Run pydocstyle
|
||||
@@ -53,10 +53,10 @@ jobs:
|
||||
name: Verify flake8
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v4.3.0
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Run flake8
|
||||
@@ -68,10 +68,10 @@ jobs:
|
||||
name: Verify pylint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v4.3.0
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Run pylint
|
||||
@@ -83,8 +83,8 @@ jobs:
|
||||
name: Shellcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-depth: 1
|
||||
- name: Run ShellCheck
|
||||
uses: ludeeus/action-shellcheck@master
|
||||
|
||||
4
.github/workflows/readme.yml
vendored
4
.github/workflows/readme.yml
vendored
@@ -8,9 +8,9 @@ jobs:
|
||||
name: Verify readme
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-depth: 1
|
||||
- name: Run readme test
|
||||
run: |
|
||||
error=0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
repos:
|
||||
- repo: https://github.com/ansible/ansible-lint.git
|
||||
rev: v6.22.0
|
||||
rev: v24.5.0
|
||||
hooks:
|
||||
- id: ansible-lint
|
||||
always_run: false
|
||||
@@ -21,20 +21,20 @@ repos:
|
||||
--parseable
|
||||
--nocolor
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
rev: v1.32.0
|
||||
rev: v1.35.1
|
||||
hooks:
|
||||
- id: yamllint
|
||||
files: \.(yaml|yml)$
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 6.0.0
|
||||
rev: 7.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
- repo: https://github.com/pycqa/pydocstyle
|
||||
rev: 6.0.0
|
||||
rev: 6.3.0
|
||||
hooks:
|
||||
- id: pydocstyle
|
||||
- repo: https://github.com/pycqa/pylint
|
||||
rev: v3.0.2
|
||||
rev: v3.2.2
|
||||
hooks:
|
||||
- id: pylint
|
||||
args:
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -21,7 +21,7 @@ FreeIPA versions 4.4.0 and up are supported by the ipaautomountkey module.
|
||||
Requirements
|
||||
------------
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -21,7 +21,7 @@ FreeIPA versions 4.4.0 and up are supported by the ipaautomountlocation module.
|
||||
Requirements
|
||||
------------
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -21,7 +21,7 @@ FreeIPA versions 4.4.0 and up are supported by the ipaautomountmap module.
|
||||
Requirements
|
||||
------------
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -25,7 +25,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
* Some tool to generate a certificate signing request (CSR) might be needed, like `openssl`.
|
||||
|
||||
**Node**
|
||||
|
||||
@@ -25,7 +25,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -23,7 +23,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -21,7 +21,7 @@ FreeIPA versions 4.4.0 and up are supported by the ipadnsforwardzone module.
|
||||
Requirements
|
||||
------------
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -23,7 +23,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
|
||||
**Node**
|
||||
@@ -133,6 +133,22 @@ Example playbook to enable a zone:
|
||||
state: enabled
|
||||
```
|
||||
|
||||
Example playbook to allow per-zone privilege delegation:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to enable per-zone privilege delegation
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Enable privilege delegation.
|
||||
ipadnszone:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testzone.local
|
||||
permission: true
|
||||
```
|
||||
|
||||
|
||||
Example playbook to remove a zone:
|
||||
```yaml
|
||||
@@ -223,6 +239,7 @@ Variable | Description | Required
|
||||
`ttl`| Time to live for records at zone apex | no
|
||||
`default_ttl`| Time to live for records without explicit TTL definition | no
|
||||
`nsec3param_rec`| NSEC3PARAM record for zone in format: hash_algorithm flags iterations salt | no
|
||||
`permission` \| `managedby` | Set per-zone access delegation permission. | no
|
||||
`skip_overlap_check`| Force DNS zone creation even if it will overlap with an existing zone | no
|
||||
`skip_nameserver_check` | Force DNS zone creation even if nameserver is not resolvable | no
|
||||
|
||||
@@ -238,4 +255,6 @@ Variable | Description | Returned When
|
||||
Authors
|
||||
=======
|
||||
|
||||
Sergio Oliveira Campos
|
||||
- Sergio Oliveira Campos
|
||||
- Thomas Woerner
|
||||
- Rafael Jeffman
|
||||
|
||||
@@ -29,7 +29,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
@@ -130,6 +130,45 @@ And ensure the presence of the groups with this example playbook:
|
||||
groups: "{{ groups }}"
|
||||
```
|
||||
|
||||
Example playbook to rename a group:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to rename a single group
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: Rename group appops to webops
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: appops
|
||||
rename: webops
|
||||
state: renamed
|
||||
```
|
||||
|
||||
Several groups can also be renamed with a single task, as in the example playbook:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to rename multiple groups
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: Rename group1 to newgroup1 and group2 to newgroup2
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: group1
|
||||
rename: newgroup1
|
||||
- name: group2
|
||||
rename: newgroup2
|
||||
state: renamed
|
||||
```
|
||||
|
||||
Example playbook to add users to a group:
|
||||
|
||||
```yaml
|
||||
@@ -262,11 +301,13 @@ 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. | no
|
||||
`idoverrideuser` | List of user ID overrides to manage. Only usable with IPA versions 4.8.7 and up.| no
|
||||
`rename` \| `new_name` | Rename the user object to the new name string. Only usable with `state: renamed`. | no
|
||||
`action` | Work on group or member level. It can be on of `member` or `group` and defaults to `group`. | no
|
||||
`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | yes
|
||||
`state` | The state to ensure. It can be one of `present`, `absent` or `renamed`, default: `present`. | yes
|
||||
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Thomas Woerner
|
||||
- Thomas Woerner
|
||||
- Rafael Jeffman
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -24,7 +24,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -26,7 +26,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -29,7 +29,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -29,7 +29,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -37,7 +37,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -29,7 +29,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
106
README-inventory-plugin-freeipa.md
Normal file
106
README-inventory-plugin-freeipa.md
Normal file
@@ -0,0 +1,106 @@
|
||||
Inventory plugin
|
||||
================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
|
||||
The inventory plugin compiles a dynamic inventory from IPA domain. The servers can be filtered by their role(s).
|
||||
|
||||
This plugin is using the Python requests binding, that is only available for Python 3.7 and up.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
* Dynamic inventory
|
||||
|
||||
|
||||
Supported FreeIPA Versions
|
||||
--------------------------
|
||||
|
||||
FreeIPA versions 4.6.0 and up are supported by the inventory plugin.
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
The inventory plugin is automatically enabled from the Ansible collection or from the top directory of the git repo if the `plugins` folder is linked to `~/.ansible`.
|
||||
|
||||
If `ansible.cfg` was modified to point to the roles and modules with `roles_path`, `library` and `module_utils` tag, then it is needed to set `inventory_plugins` also:
|
||||
|
||||
```
|
||||
inventory_plugins = /my/dir/ansible-freeipa/plugins/inventory
|
||||
```
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Example inventory file "freeipa.yml":
|
||||
|
||||
```yml
|
||||
---
|
||||
plugin: freeipa
|
||||
server: server.ipa.local
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
```
|
||||
|
||||
Example inventory file "freeipa.yml" with server TLS certificate verification using local copy of `/etc/ipa/ca.crt` from the server:
|
||||
|
||||
```yml
|
||||
---
|
||||
plugin: freeipa
|
||||
server: server.ipa.local
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
verify: ca.crt
|
||||
```
|
||||
|
||||
|
||||
How to use the plugin
|
||||
---------------------
|
||||
|
||||
With the `ansible-inventory` command it is possible to show the generated inventorey:
|
||||
|
||||
```bash
|
||||
ansible-inventory -v -i freeipa.yml --graph
|
||||
```
|
||||
|
||||
Example inventory file "freeipa.yml" for use with `playbooks/config/retrieve-config.yml`:
|
||||
|
||||
```yml
|
||||
---
|
||||
plugin: freeipa
|
||||
server: server.ipa.local
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
inventory_group: ipaserver
|
||||
```
|
||||
|
||||
```bash
|
||||
ansible-playbook -u root -i ipa.yml playbooks/config/retrieve-config.yml
|
||||
```
|
||||
|
||||
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
|
||||
`server` | The FQDN of server to start the scan. (string) | yes
|
||||
`verify` | The server TLS certificate file for verification (/etc/ipa/ca.crt). Turned off if not set. (string) | yes
|
||||
`role` | The role(s) of the server. If several roles are given, only servers that have all the roles are returned. (list of strings) (choices: "IPA master", "CA server", "KRA server", "DNS server", "AD trust controller", "AD trust agent") | no
|
||||
`inventory_group` | The inventory group to create. The default group name is "ipaservers". | no
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
- Thomas Woerner
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -25,7 +25,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -23,7 +23,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -25,7 +25,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FReeIPA version (see above)
|
||||
@@ -282,6 +282,65 @@ Example playbook to allow users, groups, hosts or hostgroups to retrieve a keyta
|
||||
```
|
||||
|
||||
|
||||
Example playbook to ensure presence of serveral services in a single task:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage IPA service.
|
||||
hosts: ipaserver
|
||||
|
||||
tasks:
|
||||
- name: Ensure services are present
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services:
|
||||
- name: HTTP/www.example.com
|
||||
principal:
|
||||
- host/host1.example.com
|
||||
- name: mysvc/www.example.com
|
||||
pac_type: NONE
|
||||
ok_as_delegate: yes
|
||||
ok_to_auth_as_delegate: yes
|
||||
- name: HTTP/www.example.com
|
||||
allow_create_keytab_user:
|
||||
- user01
|
||||
- user02
|
||||
allow_create_keytab_group:
|
||||
- group01
|
||||
- group02
|
||||
allow_create_keytab_host:
|
||||
- host1.example.com
|
||||
- host2.example.com
|
||||
allow_create_keytab_hostgroup:
|
||||
- hostgroup01
|
||||
- hostgroup02
|
||||
- name: mysvc/host2.example.com
|
||||
auth_ind: otp,radius
|
||||
```
|
||||
|
||||
|
||||
Example playbook to ensure presence of serveral services in a single task with `member` `action`:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage IPA service.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure service host members are present
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services:
|
||||
- name: HTTP/www1.example.com
|
||||
host: host1.example.com
|
||||
- name: HTTP/www2.example.com
|
||||
host: host2.example.com
|
||||
action: member
|
||||
```
|
||||
|
||||
|
||||
Variables
|
||||
---------
|
||||
|
||||
@@ -291,7 +350,15 @@ Variable | Description | Required
|
||||
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
|
||||
`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 yes. (bool) | no
|
||||
`name` \| `service` | The list of service name strings. | yes
|
||||
`name` \| `service` | The list of service name strings. `name` with *service variables* or `services` containing *service variables* need to be used. | no
|
||||
`action` | Work on service or member level. It can be on of `member` or `service` and defaults to `service`. | no
|
||||
`state` | The state to ensure. It can be one of `present`, `absent`, or `disabled`, default: `present`. | no
|
||||
|
||||
|
||||
**Service Variables:**
|
||||
|
||||
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` 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
|
||||
@@ -310,11 +377,9 @@ Variable | Description | Required
|
||||
`allow_retrieve_keytab_group` \| `ipaallowedtoperform_read_keys_group` | Groups allowed to retrieve a keytab of this host. | no
|
||||
`allow_retrieve_keytab_host` \| `ipaallowedtoperform_read_keys_host` | Hosts allowed to retrieve a keytab from of host. | no
|
||||
`allow_retrieve_keytab_hostgroup` \| `ipaallowedtoperform_read_keys_hostgroup` | Host groups allowed to retrieve a keytab of this host. | no
|
||||
`continue` | Continuous mode: don't stop on errors. Valid only if `state` is `absent`. Default: `no` (bool) | no
|
||||
`smb` | Service is an SMB service. If set, `cifs/` will be prefixed to the service name if needed. | no
|
||||
`netbiosname` | NETBIOS name for the SMB service. Only with `smb: yes`. | no
|
||||
`action` | Work on service or member level. It can be on of `member` or `service` and defaults to `service`. | no
|
||||
`state` | The state to ensure. It can be one of `present`, `absent`, or `disabled`, default: `present`. | no
|
||||
`continue` \| `delete_continue` | Continuous mode: don't stop on errors. Valid only if `state` is `absent`. Default: `no` (bool) | no
|
||||
|
||||
|
||||
Authors
|
||||
|
||||
@@ -24,7 +24,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -24,7 +24,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -24,7 +24,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -24,7 +24,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
@@ -93,6 +93,26 @@ Example playbook to make sure sudocmds are not present in Sudo Rule:
|
||||
state: absent
|
||||
```
|
||||
|
||||
|
||||
Example playbook to ensure a Group of RunAs User is present in sudo rule:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage sudorule member
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
gather_facts: no
|
||||
|
||||
tasks:
|
||||
- name: Ensure sudorule 'runasuser' has 'ipasuers' group as runas users.
|
||||
ipasudorule:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testrule1
|
||||
runasuser_group: ipausers
|
||||
action: member
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure Sudo Rule is absent:
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -21,7 +21,7 @@ Requirements
|
||||
|
||||
**Controller**
|
||||
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
@@ -279,7 +279,6 @@ Example playbook to disable a user:
|
||||
|
||||
This can also be done as an alternative with the `users` variable containing only names.
|
||||
|
||||
|
||||
Example playbook to enable users:
|
||||
|
||||
```yaml
|
||||
@@ -298,6 +297,22 @@ Example playbook to enable users:
|
||||
|
||||
This can also be done as an alternative with the `users` variable containing only names.
|
||||
|
||||
Example playbook to rename users:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to handle users
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
# Rename user pinky to reddy
|
||||
- ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: pinky
|
||||
rename: reddy
|
||||
state: renamed
|
||||
```
|
||||
|
||||
Example playbook to unlock users:
|
||||
|
||||
@@ -401,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`, `unlocked` or `undeleted`, 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
|
||||
|
||||
|
||||
|
||||
@@ -458,10 +473,10 @@ Variable | Description | Required
|
||||
`smb_profile_path:` \| `ipantprofilepath` | SMB profile path, in UNC format. Requires FreeIPA version 4.8.0+. | no
|
||||
`smb_home_dir` \| `ipanthomedirectory` | SMB Home Directory, in UNC format. Requires FreeIPA version 4.8.0+. | no
|
||||
`smb_home_drive` \| `ipanthomedirectorydrive` | SMB Home Directory Drive, a single upercase letter (A-Z) followed by a colon (:), for example "U:". Requires FreeIPA version 4.8.0+. | no
|
||||
`rename` \| `new_name` | Rename the user object to the new name string. Only usable with `state: renamed`. | no
|
||||
`nomembers` | Suppress processing of membership attributes. (bool) | no
|
||||
|
||||
|
||||
|
||||
Return Values
|
||||
=============
|
||||
|
||||
@@ -477,5 +492,5 @@ Variable | Description | Returned When
|
||||
Authors
|
||||
=======
|
||||
|
||||
Thomas Woerner
|
||||
Rafael Jeffman
|
||||
- Thomas Woerner
|
||||
- Rafael Jeffman
|
||||
|
||||
@@ -24,7 +24,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
15
README.md
15
README.md
@@ -13,6 +13,7 @@ Features
|
||||
* Repair mode for clients
|
||||
* Backup and restore, also to and from controller
|
||||
* Smartcard setup for servers and clients
|
||||
* Inventory plugin freeipa
|
||||
* Modules for automembership rule management
|
||||
* Modules for automount key management
|
||||
* Modules for automount location management
|
||||
@@ -73,7 +74,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
@@ -108,9 +109,10 @@ You can use the roles directly within the top directory of the git repo, but to
|
||||
You can either adapt ansible.cfg:
|
||||
|
||||
```
|
||||
roles_path = /my/dir/ansible-freeipa/roles
|
||||
library = /my/dir/ansible-freeipa/plugins/modules
|
||||
module_utils = /my/dir/ansible-freeipa/plugins/module_utils
|
||||
roles_path = /my/dir/ansible-freeipa/roles
|
||||
library = /my/dir/ansible-freeipa/plugins/modules
|
||||
module_utils = /my/dir/ansible-freeipa/plugins/module_utils
|
||||
inventory_plugins = /my/dir/ansible-freeipa/plugins/inventory
|
||||
```
|
||||
|
||||
Or you can link the directories:
|
||||
@@ -470,3 +472,8 @@ Modules in plugin/modules
|
||||
* [ipavault](README-vault.md)
|
||||
|
||||
If you want to write a new module please read [writing a new module](plugins/modules/README.md).
|
||||
|
||||
Inventory plugins in plugin/inventory
|
||||
=====================================
|
||||
|
||||
* [freeipa](README-inventory-plugin-freeipa.md)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
requires_ansible: ">=2.13"
|
||||
requires_ansible: ">=2.15.0"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
---
|
||||
- name: Certificate manage example
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
- name: Playbook to manage sudorule member
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
gather_facts: no
|
||||
|
||||
tasks:
|
||||
- name: Ensure sudorule 'runasuser' do not have 'ipasuers' group as runas users.
|
||||
ipasudorule:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testrule1
|
||||
runasuser_group: ipausers
|
||||
action: member
|
||||
state: absent
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
- name: Playbook to manage sudorule member
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
gather_facts: no
|
||||
|
||||
tasks:
|
||||
- name: Ensure sudorule 'runasuser' has 'ipasuers' group as runas users.
|
||||
ipasudorule:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testrule1
|
||||
runasuser_group: ipausers
|
||||
action: member
|
||||
@@ -56,5 +56,5 @@ options:
|
||||
Continuous mode. Don't stop on errors. Valid only if `state` is `absent`.
|
||||
aliases: ["continue"]
|
||||
type: bool
|
||||
default: True
|
||||
default: true
|
||||
"""
|
||||
|
||||
191
plugins/inventory/freeipa.py
Normal file
191
plugins/inventory/freeipa.py
Normal file
@@ -0,0 +1,191 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2024 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 = """
|
||||
---
|
||||
name: freeipa
|
||||
version_added: "1.13.0"
|
||||
short_description: Compiles a dynamic inventory from IPA domain
|
||||
description: |
|
||||
Compiles a dynamic inventory from IPA domain, filters servers by role(s).
|
||||
options:
|
||||
plugin:
|
||||
description: Marks this as an instance of the "freeipa" plugin.
|
||||
required: True
|
||||
choices: ["freeipa"]
|
||||
ipaadmin_principal:
|
||||
description: The admin principal.
|
||||
default: admin
|
||||
type: str
|
||||
ipaadmin_password:
|
||||
description: The admin password.
|
||||
required: true
|
||||
type: str
|
||||
server:
|
||||
description: FQDN of server to start the scan.
|
||||
type: str
|
||||
required: true
|
||||
verify:
|
||||
description: |
|
||||
The server TLS certificate file for verification (/etc/ipa/ca.crt).
|
||||
Turned off if not set.
|
||||
type: str
|
||||
required: false
|
||||
role:
|
||||
description: |
|
||||
The role(s) of the server. If several roles are given, only servers
|
||||
that have all the roles are returned.
|
||||
type: list
|
||||
elements: str
|
||||
choices: ["IPA master", "CA server", "KRA server", "DNS server",
|
||||
"AD trust controller", "AD trust agent"]
|
||||
required: false
|
||||
inventory_group:
|
||||
description: |
|
||||
The inventory group to create. The default group name is "ipaservers".
|
||||
type: str
|
||||
default: ipaservers
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
# inventory.config file in YAML format
|
||||
plugin: freeipa
|
||||
server: ipaserver-01.ipa.local
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
|
||||
# inventory.config file in YAML format with server TLS certificate verification
|
||||
plugin: freeipa
|
||||
server: ipaserver-01.ipa.local
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
verify: ca.crt
|
||||
"""
|
||||
|
||||
import os
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
requests = None
|
||||
try:
|
||||
import urllib3
|
||||
except ImportError:
|
||||
urllib3 = None
|
||||
|
||||
from ansible import constants
|
||||
from ansible.errors import AnsibleParserError
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin
|
||||
from ansible.module_utils.six.moves.urllib.parse import quote
|
||||
|
||||
|
||||
class InventoryModule(BaseInventoryPlugin):
|
||||
|
||||
NAME = 'freeipa'
|
||||
|
||||
def verify_file(self, path):
|
||||
# pylint: disable=super-with-arguments
|
||||
if super(InventoryModule, self).verify_file(path):
|
||||
_name, ext = os.path.splitext(path)
|
||||
if ext in constants.YAML_FILENAME_EXTENSIONS:
|
||||
return True
|
||||
return False
|
||||
|
||||
def parse(self, inventory, loader, path, cache=False):
|
||||
# pylint: disable=super-with-arguments
|
||||
super(InventoryModule, self).parse(inventory, loader, path,
|
||||
cache=cache)
|
||||
self._read_config_data(path) # This also loads the cache
|
||||
|
||||
self.get_option("plugin")
|
||||
|
||||
if requests is None:
|
||||
raise AnsibleParserError("The required Python library "
|
||||
"'requests' could not be imported.")
|
||||
|
||||
ipaadmin_principal = self.get_option("ipaadmin_principal")
|
||||
ipaadmin_password = self.get_option("ipaadmin_password")
|
||||
server = self.get_option("server")
|
||||
verify = self.get_option("verify")
|
||||
role = self.get_option("role")
|
||||
inventory_group = self.get_option("inventory_group")
|
||||
|
||||
if verify is not None:
|
||||
if not os.path.exists(verify):
|
||||
raise AnsibleParserError("ERROR: Could not load %s" % verify)
|
||||
else:
|
||||
verify = False
|
||||
# Disable certificate verification warning without certificate
|
||||
# as long as urllib3 could have been loaded.
|
||||
if urllib3 is not None:
|
||||
urllib3.disable_warnings(
|
||||
urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
self.inventory.add_group(inventory_group)
|
||||
|
||||
ipa_url = "https://%s/ipa" % server
|
||||
|
||||
s = requests.Session()
|
||||
s.headers.update({"referer": ipa_url})
|
||||
s.headers.update({"Content-Type":
|
||||
"application/x-www-form-urlencoded"})
|
||||
s.headers.update({"Accept": "text/plain"})
|
||||
|
||||
data = 'user=%s&password=%s' % (quote(ipaadmin_principal, safe=''),
|
||||
quote(ipaadmin_password, safe=''))
|
||||
response = s.post("%s/session/login_password" % ipa_url,
|
||||
data=data, verify=verify)
|
||||
|
||||
# Now use json API
|
||||
s.headers.update({"Content-Type": "application/json"})
|
||||
|
||||
kw_args = {}
|
||||
if role is not None:
|
||||
kw_args["servrole"] = role
|
||||
json_data = {
|
||||
"method" : "server_find",
|
||||
"params": [[], kw_args],
|
||||
"id": 0
|
||||
}
|
||||
response = s.post("%s/session/json" % ipa_url, json=json_data,
|
||||
verify=verify)
|
||||
json_res = response.json()
|
||||
|
||||
error = json_res.get("error")
|
||||
if error is not None:
|
||||
raise AnsibleParserError("ERROR: %s" % to_native(error))
|
||||
|
||||
if "result" in json_res and "result" in json_res["result"]:
|
||||
res = json_res["result"].get("result")
|
||||
if isinstance(res, list):
|
||||
for server in res:
|
||||
self.inventory.add_host(server["cn"][0],
|
||||
group=inventory_group)
|
||||
@@ -25,12 +25,24 @@ from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
__all__ = ["gssapi", "netaddr", "api", "ipalib_errors", "Env",
|
||||
__all__ = ["DEBUG_COMMAND_ALL", "DEBUG_COMMAND_LIST",
|
||||
"DEBUG_COMMAND_COUNT", "DEBUG_COMMAND_BATCH",
|
||||
"gssapi", "netaddr", "api", "ipalib_errors", "Env",
|
||||
"DEFAULT_CONFIG", "LDAP_GENERALIZED_TIME_FORMAT",
|
||||
"kinit_password", "kinit_keytab", "run", "DN", "VERSION",
|
||||
"paths", "tasks", "get_credentials_if_valid", "Encoding",
|
||||
"DNSName", "getargspec", "certificate_loader",
|
||||
"write_certificate_list", "boolean", "template_str"]
|
||||
"write_certificate_list", "boolean", "template_str",
|
||||
"urlparse", "normalize_sshpubkey"]
|
||||
|
||||
DEBUG_COMMAND_ALL = 0b1111
|
||||
# Print the while command list:
|
||||
DEBUG_COMMAND_LIST = 0b0001
|
||||
# Print the number of commands:
|
||||
DEBUG_COMMAND_COUNT = 0b0010
|
||||
# Print information about the batch slice size and currently executed batch
|
||||
# slice:
|
||||
DEBUG_COMMAND_BATCH = 0b0100
|
||||
|
||||
import os
|
||||
# ansible-freeipa requires locale to be C, IPA requires utf-8.
|
||||
@@ -42,7 +54,9 @@ import tempfile
|
||||
import shutil
|
||||
import socket
|
||||
import base64
|
||||
import binascii
|
||||
import ast
|
||||
import time
|
||||
from datetime import datetime
|
||||
from contextlib import contextmanager
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
@@ -86,9 +100,13 @@ try:
|
||||
from ipalib.constants import DEFAULT_CONFIG, LDAP_GENERALIZED_TIME_FORMAT
|
||||
|
||||
try:
|
||||
from ipalib.install.kinit import kinit_password, kinit_keytab
|
||||
from ipalib.kinit import kinit_password, kinit_keytab
|
||||
except ImportError:
|
||||
from ipapython.ipautil import kinit_password, kinit_keytab
|
||||
try:
|
||||
from ipalib.install.kinit import kinit_password, kinit_keytab
|
||||
except ImportError:
|
||||
# pre 4.5.0
|
||||
from ipapython.ipautil import kinit_password, kinit_keytab
|
||||
from ipapython.ipautil import run
|
||||
from ipapython.ipautil import template_str
|
||||
from ipapython.dn import DN
|
||||
@@ -147,6 +165,13 @@ try:
|
||||
except ImportError:
|
||||
_dcerpc_bindings_installed = False # pylint: disable=invalid-name
|
||||
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
except ImportError:
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlparse
|
||||
|
||||
from ipalib.util import normalize_sshpubkey
|
||||
|
||||
except ImportError as _err:
|
||||
ANSIBLE_FREEIPA_MODULE_IMPORT_ERROR = str(_err)
|
||||
|
||||
@@ -464,20 +489,22 @@ def _afm_convert(value):
|
||||
return value
|
||||
|
||||
|
||||
def module_params_get(module, name, allow_empty_string=False):
|
||||
def module_params_get(module, name, allow_empty_list_item=False):
|
||||
value = _afm_convert(module.params.get(name))
|
||||
|
||||
# Fail on empty strings in the list or if allow_empty_string is True
|
||||
# if there is another entry in the list together with the empty
|
||||
# string.
|
||||
# Fail on empty strings in the list or if allow_empty_list_item is True
|
||||
# if there is another entry in the list together with the empty string.
|
||||
# Due to an issue in Ansible it is possible to use the empty string
|
||||
# "" for lists with choices, even if the empty list is not part of
|
||||
# the choices.
|
||||
# Ansible issue https://github.com/ansible/ansible/issues/77108
|
||||
if isinstance(value, list):
|
||||
for val in value:
|
||||
if isinstance(val, (str, unicode)) and not val:
|
||||
if not allow_empty_string:
|
||||
if (
|
||||
isinstance(val, (str, unicode)) # pylint: disable=W0012,E0606
|
||||
and not val
|
||||
):
|
||||
if not allow_empty_list_item:
|
||||
module.fail_json(
|
||||
msg="Parameter '%s' contains an empty string" %
|
||||
name)
|
||||
@@ -489,8 +516,8 @@ def module_params_get(module, name, allow_empty_string=False):
|
||||
return value
|
||||
|
||||
|
||||
def module_params_get_lowercase(module, name, allow_empty_string=False):
|
||||
value = module_params_get(module, name, allow_empty_string)
|
||||
def module_params_get_lowercase(module, name, allow_empty_list_item=False):
|
||||
value = module_params_get(module, name, allow_empty_list_item)
|
||||
if isinstance(value, list):
|
||||
value = [v.lower() for v in value]
|
||||
if isinstance(value, (str, unicode)):
|
||||
@@ -498,6 +525,48 @@ def module_params_get_lowercase(module, name, allow_empty_string=False):
|
||||
return value
|
||||
|
||||
|
||||
def module_params_get_with_type_cast(
|
||||
module, name, datatype, allow_empty=False
|
||||
):
|
||||
"""
|
||||
Retrieve value set for module parameter as a specific data type.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module: AnsibleModule
|
||||
The module from where to get the parameter value from.
|
||||
name: string
|
||||
The name of the parameter to retrieve.
|
||||
datatype: type
|
||||
The type to convert the value to, if value is not empty.
|
||||
allow_empty: bool
|
||||
Allow an empty string for non list parameters or a list
|
||||
containing (only) an empty string item. This is used for
|
||||
resetting parameters to the default value.
|
||||
|
||||
"""
|
||||
value = module_params_get(module, name, allow_empty)
|
||||
if not allow_empty and value == "":
|
||||
module.fail_json(
|
||||
msg="Argument '%s' must not be an empty string" % (name,)
|
||||
)
|
||||
if value is not None and value != "":
|
||||
try:
|
||||
if datatype is bool:
|
||||
# We let Ansible handle bool values
|
||||
value = boolean(value)
|
||||
else:
|
||||
value = datatype(value)
|
||||
except ValueError:
|
||||
module.fail_json(
|
||||
msg="Invalid value '%s' for argument '%s'" % (value, name)
|
||||
)
|
||||
except TypeError as terr:
|
||||
# If Ansible fails to parse a boolean, it will raise TypeError
|
||||
module.fail_json(msg="Param '%s': %s" % (name, str(terr)))
|
||||
return value
|
||||
|
||||
|
||||
def api_get_domain():
|
||||
return api.env.domain
|
||||
|
||||
@@ -576,6 +645,7 @@ def encode_certificate(cert):
|
||||
Encode a certificate using base64.
|
||||
|
||||
It also takes FreeIPA and Python versions into account.
|
||||
This is used to convert the certificates returned by find and show.
|
||||
"""
|
||||
if isinstance(cert, (str, unicode, bytes)):
|
||||
encoded = base64.b64encode(cert)
|
||||
@@ -586,6 +656,33 @@ def encode_certificate(cert):
|
||||
return encoded
|
||||
|
||||
|
||||
def convert_input_certificates(module, certs, state):
|
||||
"""
|
||||
Convert certificates.
|
||||
|
||||
Remove all newlines and white spaces from the certificates.
|
||||
This is used on input parameter certificates of modules.
|
||||
"""
|
||||
if certs is None:
|
||||
return None
|
||||
|
||||
_certs = []
|
||||
for cert in certs:
|
||||
try:
|
||||
_cert = base64.b64encode(base64.b64decode(cert)).decode("ascii")
|
||||
except (TypeError, binascii.Error) as e:
|
||||
# Idempotency: Do not fail for an invalid cert for state absent.
|
||||
# The invalid certificate can not be set in FreeIPA.
|
||||
if state == "absent":
|
||||
continue
|
||||
module.fail_json(
|
||||
msg="Certificate %s: Base64 decoding failed: %s" %
|
||||
(repr(cert), str(e)))
|
||||
_certs.append(_cert)
|
||||
|
||||
return _certs
|
||||
|
||||
|
||||
def load_cert_from_str(cert):
|
||||
cert = cert.strip()
|
||||
if not cert.startswith("-----BEGIN CERTIFICATE-----"):
|
||||
@@ -1045,7 +1142,7 @@ class IPAAnsibleModule(AnsibleModule):
|
||||
finally:
|
||||
temp_kdestroy(ccache_dir, ccache_name)
|
||||
|
||||
def params_get(self, name, allow_empty_string=False):
|
||||
def params_get(self, name, allow_empty_list_item=False):
|
||||
"""
|
||||
Retrieve value set for module parameter.
|
||||
|
||||
@@ -1053,13 +1150,13 @@ class IPAAnsibleModule(AnsibleModule):
|
||||
----------
|
||||
name: string
|
||||
The name of the parameter to retrieve.
|
||||
allow_empty_string: bool
|
||||
allow_empty_list_item: bool
|
||||
The parameter allowes to have empty strings in a list
|
||||
|
||||
"""
|
||||
return module_params_get(self, name, allow_empty_string)
|
||||
return module_params_get(self, name, allow_empty_list_item)
|
||||
|
||||
def params_get_lowercase(self, name, allow_empty_string=False):
|
||||
def params_get_lowercase(self, name, allow_empty_list_item=False):
|
||||
"""
|
||||
Retrieve value set for module parameter as lowercase, if not None.
|
||||
|
||||
@@ -1067,11 +1164,34 @@ class IPAAnsibleModule(AnsibleModule):
|
||||
----------
|
||||
name: string
|
||||
The name of the parameter to retrieve.
|
||||
allow_empty_string: bool
|
||||
allow_empty_list_item: bool
|
||||
The parameter allowes to have empty strings in a list
|
||||
|
||||
"""
|
||||
return module_params_get_lowercase(self, name, allow_empty_string)
|
||||
return module_params_get_lowercase(self, name, allow_empty_list_item)
|
||||
|
||||
def params_get_with_type_cast(
|
||||
self, name, datatype, allow_empty=True
|
||||
):
|
||||
"""
|
||||
Retrieve value set for module parameter as a specific data type.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: string
|
||||
The name of the parameter to retrieve.
|
||||
datatype: type
|
||||
The type to convert the value to, if not empty.
|
||||
datatype: type
|
||||
The type to convert the value to, if value is not empty.
|
||||
allow_empty: bool
|
||||
Allow an empty string for non list parameters or a list
|
||||
containing (only) an empty string item. This is used for
|
||||
resetting parameters to the default value.
|
||||
|
||||
"""
|
||||
return module_params_get_with_type_cast(
|
||||
self, name, datatype, allow_empty)
|
||||
|
||||
def params_fail_used_invalid(self, invalid_params, state, action=None):
|
||||
"""
|
||||
@@ -1239,7 +1359,8 @@ class IPAAnsibleModule(AnsibleModule):
|
||||
def execute_ipa_commands(self, commands, result_handler=None,
|
||||
exception_handler=None,
|
||||
fail_on_member_errors=False,
|
||||
**handlers_user_args):
|
||||
batch=False, batch_slice_size=100, debug=False,
|
||||
keeponly=None, **handlers_user_args):
|
||||
"""
|
||||
Execute IPA API commands from command list.
|
||||
|
||||
@@ -1256,6 +1377,16 @@ class IPAAnsibleModule(AnsibleModule):
|
||||
Returns True to continue to next command, else False
|
||||
fail_on_member_errors: bool
|
||||
Use default member error handler handler member_error_handler
|
||||
batch: bool
|
||||
Enable batch command use to speed up processing
|
||||
batch_slice_size: integer
|
||||
Maximum mumber of commands processed in a slice with the batch
|
||||
command
|
||||
keeponly: list of string
|
||||
The attributes to keep in the results returned from the commands
|
||||
Default: None (Keep all)
|
||||
debug: integer
|
||||
Enable debug output for the exection using DEBUG_COMMAND_*
|
||||
handlers_user_args: dict (user args mapping)
|
||||
The user args to pass to result_handler and exception_handler
|
||||
functions
|
||||
@@ -1325,34 +1456,123 @@ class IPAAnsibleModule(AnsibleModule):
|
||||
if "errors" in argspec.args:
|
||||
handlers_user_args["errors"] = _errors
|
||||
|
||||
if debug & DEBUG_COMMAND_LIST:
|
||||
self.tm_warn("commands: %s" % repr(commands))
|
||||
if debug & DEBUG_COMMAND_COUNT:
|
||||
self.tm_warn("#commands: %s" % len(commands))
|
||||
|
||||
# Turn off batch use for server context when it lacks the keeponly
|
||||
# option as it lacks https://github.com/freeipa/freeipa/pull/7335
|
||||
# This is an important fix about reporting errors in the batch
|
||||
# (example: "no modifications to be performed") that results in
|
||||
# aborted processing of the batch and an error about missing
|
||||
# attribute principal. FreeIPA issue #9583
|
||||
batch_has_keeponly = "keeponly" in api.Command.batch.options
|
||||
if batch and api.env.in_server and not batch_has_keeponly:
|
||||
self.debug(
|
||||
"Turning off batch processing for batch missing keeponly")
|
||||
batch = False
|
||||
|
||||
changed = False
|
||||
for name, command, args in commands:
|
||||
try:
|
||||
if name is None:
|
||||
result = self.ipa_command_no_name(command, args)
|
||||
else:
|
||||
result = self.ipa_command(command, name, args)
|
||||
if batch:
|
||||
# batch processing
|
||||
batch_args = []
|
||||
for ci, (name, command, args) in enumerate(commands):
|
||||
if len(batch_args) < batch_slice_size:
|
||||
batch_args.append({
|
||||
"method": command,
|
||||
"params": ([name], args)
|
||||
})
|
||||
|
||||
if "completed" in result:
|
||||
if result["completed"] > 0:
|
||||
changed = True
|
||||
else:
|
||||
changed = True
|
||||
|
||||
# If result_handler is not None, call it with user args
|
||||
# defined in **handlers_user_args
|
||||
if result_handler is not None:
|
||||
result_handler(self, result, command, name, args,
|
||||
**handlers_user_args)
|
||||
|
||||
except Exception as e:
|
||||
if exception_handler is not None and \
|
||||
exception_handler(self, e, **handlers_user_args):
|
||||
if len(batch_args) < batch_slice_size and \
|
||||
ci < len(commands) - 1:
|
||||
# fill in more commands untill batch slice size is reached
|
||||
# or final slice of commands
|
||||
continue
|
||||
self.fail_json(msg="%s: %s: %s" % (command, name, str(e)))
|
||||
|
||||
if debug & DEBUG_COMMAND_BATCH:
|
||||
self.tm_warn("batch %d (size %d/%d)" %
|
||||
(ci / batch_slice_size, len(batch_args),
|
||||
batch_slice_size))
|
||||
|
||||
# run the batch command
|
||||
if batch_has_keeponly:
|
||||
result = api.Command.batch(batch_args, keeponly=keeponly)
|
||||
else:
|
||||
result = api.Command.batch(batch_args)
|
||||
|
||||
if len(batch_args) != result["count"]:
|
||||
self.fail_json(
|
||||
"Result size %d does not match batch size %d" % (
|
||||
result["count"], len(batch_args)))
|
||||
if result["count"] > 0:
|
||||
for ri, res in enumerate(result["results"]):
|
||||
_res = res.get("result", None)
|
||||
if not batch_has_keeponly and keeponly is not None \
|
||||
and isinstance(_res, dict):
|
||||
res["result"] = dict(
|
||||
filter(lambda x: x[0] in keeponly,
|
||||
_res.items())
|
||||
)
|
||||
|
||||
if "error" not in res or res["error"] is None:
|
||||
if result_handler is not None:
|
||||
result_handler(
|
||||
self, res,
|
||||
batch_args[ri]["method"],
|
||||
batch_args[ri]["params"][0][0],
|
||||
batch_args[ri]["params"][1],
|
||||
**handlers_user_args)
|
||||
changed = True
|
||||
else:
|
||||
_errors.append(
|
||||
"%s: %s: %s" %
|
||||
(batch_args[ri]["method"],
|
||||
str(batch_args[ri]["params"][0][0]),
|
||||
res["error"]))
|
||||
# clear batch command list (python2 compatible)
|
||||
del batch_args[:]
|
||||
else:
|
||||
# no batch processing
|
||||
for name, command, args in commands:
|
||||
try:
|
||||
if name is None:
|
||||
result = self.ipa_command_no_name(command, args)
|
||||
else:
|
||||
result = self.ipa_command(command, name, args)
|
||||
|
||||
if "completed" in result:
|
||||
if result["completed"] > 0:
|
||||
changed = True
|
||||
else:
|
||||
changed = True
|
||||
|
||||
# Handle keeponly
|
||||
res = result.get("result", None)
|
||||
if keeponly is not None and isinstance(res, dict):
|
||||
result["result"] = dict(
|
||||
filter(lambda x: x[0] in keeponly, res.items())
|
||||
)
|
||||
|
||||
# If result_handler is not None, call it with user args
|
||||
# defined in **handlers_user_args
|
||||
if result_handler is not None:
|
||||
result_handler(self, result, command, name, args,
|
||||
**handlers_user_args)
|
||||
|
||||
except Exception as e:
|
||||
if exception_handler is not None and \
|
||||
exception_handler(self, e, **handlers_user_args):
|
||||
continue
|
||||
self.fail_json(msg="%s: %s: %s" % (command, name, str(e)))
|
||||
|
||||
# Fail on errors from result_handler and exception_handler
|
||||
if len(_errors) > 0:
|
||||
self.fail_json(msg=", ".join(_errors))
|
||||
|
||||
return changed
|
||||
|
||||
def tm_warn(self, warning):
|
||||
ts = time.time()
|
||||
# pylint: disable=super-with-arguments
|
||||
super(IPAAnsibleModule, self).warn("%f %s" % (ts, warning))
|
||||
|
||||
@@ -450,6 +450,10 @@ def main():
|
||||
commands = []
|
||||
|
||||
for name in names:
|
||||
_type = None
|
||||
inclusive_add, inclusive_del = [], []
|
||||
exclusive_add, exclusive_del = [], []
|
||||
|
||||
# Make sure automember rule exists
|
||||
res_find = find_automember(ansible_module, name, automember_type)
|
||||
|
||||
@@ -495,16 +499,12 @@ def main():
|
||||
transform_conditions(inclusive),
|
||||
res_find.get("automemberinclusiveregex", [])
|
||||
)
|
||||
else:
|
||||
inclusive_add, inclusive_del = [], []
|
||||
|
||||
if exclusive is not None:
|
||||
exclusive_add, exclusive_del = gen_add_del_lists(
|
||||
transform_conditions(exclusive),
|
||||
res_find.get("automemberexclusiveregex", [])
|
||||
)
|
||||
else:
|
||||
exclusive_add, exclusive_del = [], []
|
||||
|
||||
elif action == "member":
|
||||
if res_find is None:
|
||||
@@ -512,9 +512,7 @@ def main():
|
||||
msg="No automember '%s'" % name)
|
||||
|
||||
inclusive_add = transform_conditions(inclusive or [])
|
||||
inclusive_del = []
|
||||
exclusive_add = transform_conditions(exclusive or [])
|
||||
exclusive_del = []
|
||||
|
||||
for _inclusive in inclusive_add:
|
||||
key, regex = _inclusive.split("=", 1)
|
||||
|
||||
@@ -34,7 +34,7 @@ ANSIBLE_METADATA = {
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: ipacert
|
||||
short description: Manage FreeIPA certificates
|
||||
short_description: Manage FreeIPA certificates
|
||||
description: Manage FreeIPA certificates
|
||||
extends_documentation_fragment:
|
||||
- ipamodule_base_docs
|
||||
@@ -67,6 +67,10 @@ options:
|
||||
description: Name of the issuing certificate authority.
|
||||
type: str
|
||||
required: false
|
||||
chain:
|
||||
description: Include certificate chain in output.
|
||||
type: bool
|
||||
required: false
|
||||
serial_number:
|
||||
description: |
|
||||
Certificate serial number. Cannot be used with `state: requested`.
|
||||
@@ -102,7 +106,6 @@ options:
|
||||
required: true
|
||||
type: str
|
||||
author:
|
||||
authors:
|
||||
- Sam Morris (@yrro)
|
||||
- Rafael Guterres Jeffman (@rjeffman)
|
||||
"""
|
||||
|
||||
@@ -470,13 +470,13 @@ def main():
|
||||
"netbios_name": "netbios_name",
|
||||
"add_sids": "add_sids",
|
||||
}
|
||||
allow_empty_string = ["pac_type", "user_auth_type", "configstring"]
|
||||
reverse_field_map = {v: k for k, v in field_map.items()}
|
||||
allow_empty_list_item = ["pac_type", "user_auth_type", "configstring"]
|
||||
|
||||
params = {}
|
||||
for x in field_map:
|
||||
val = ansible_module.params_get(
|
||||
x, allow_empty_string=x in allow_empty_string)
|
||||
x, allow_empty_list_item=x in allow_empty_list_item)
|
||||
|
||||
if val is not None:
|
||||
params[field_map.get(x, x)] = val
|
||||
|
||||
@@ -180,10 +180,10 @@ def main():
|
||||
names = ansible_module.params_get("name")
|
||||
|
||||
# present
|
||||
permission = ansible_module.params_get("permission")
|
||||
attribute = ansible_module.params_get("attribute")
|
||||
permission = ansible_module.params_get_lowercase("permission")
|
||||
attribute = ansible_module.params_get_lowercase("attribute")
|
||||
membergroup = ansible_module.params_get("membergroup")
|
||||
group = ansible_module.params_get("group")
|
||||
group = ansible_module.params_get_lowercase("group")
|
||||
action = ansible_module.params_get("action")
|
||||
# state
|
||||
state = ansible_module.params_get("state")
|
||||
@@ -233,6 +233,7 @@ def main():
|
||||
|
||||
commands = []
|
||||
for name in names:
|
||||
args = {}
|
||||
# Make sure delegation exists
|
||||
res_find = find_delegation(ansible_module, name)
|
||||
|
||||
@@ -244,14 +245,7 @@ def main():
|
||||
|
||||
if action == "delegation":
|
||||
# Found the delegation
|
||||
if res_find is not None:
|
||||
# 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, "delegation_mod", args])
|
||||
else:
|
||||
if res_find is None:
|
||||
commands.append([name, "delegation_add", args])
|
||||
|
||||
elif action == "member":
|
||||
@@ -265,9 +259,7 @@ def main():
|
||||
# New attribute list (add given ones to find result)
|
||||
# Make list with unique entries
|
||||
attrs = list(set(list(res_find["attrs"]) + attribute))
|
||||
if len(attrs) > len(res_find["attrs"]):
|
||||
commands.append([name, "delegation_mod",
|
||||
{"attrs": attrs}])
|
||||
args["attrs"] = attrs
|
||||
|
||||
elif state == "absent":
|
||||
if action == "delegation":
|
||||
@@ -288,15 +280,18 @@ def main():
|
||||
if len(attrs) < 1:
|
||||
ansible_module.fail_json(
|
||||
msg="At minimum one attribute is needed.")
|
||||
|
||||
# Entries New number of attributes is smaller
|
||||
if len(attrs) < len(res_find["attrs"]):
|
||||
commands.append([name, "delegation_mod",
|
||||
{"attrs": attrs}])
|
||||
args["attrs"] = attrs
|
||||
|
||||
else:
|
||||
ansible_module.fail_json(msg="Unkown state '%s'" % state)
|
||||
|
||||
# Manage members
|
||||
if (
|
||||
args and res_find and
|
||||
not compare_args_ipa(ansible_module, args, res_find)
|
||||
):
|
||||
commands.append([name, "delegation_mod", args])
|
||||
|
||||
# Execute commands
|
||||
|
||||
changed = ansible_module.execute_ipa_commands(commands)
|
||||
|
||||
@@ -250,6 +250,8 @@ def main():
|
||||
operation = "add"
|
||||
|
||||
invalid = []
|
||||
wants_enable = False
|
||||
|
||||
if state in ["enabled", "disabled"]:
|
||||
if action == "member":
|
||||
ansible_module.fail_json(
|
||||
|
||||
@@ -1605,6 +1605,8 @@ def main():
|
||||
|
||||
res_find = find_dnsrecord(ansible_module, zone_name, name)
|
||||
|
||||
cmds = []
|
||||
|
||||
if state == 'present':
|
||||
cmds = define_commands_for_present_state(
|
||||
ansible_module, zone_name, entry, res_find)
|
||||
|
||||
@@ -142,6 +142,11 @@ options:
|
||||
salt.
|
||||
required: false
|
||||
type: str
|
||||
permission:
|
||||
description: Set per-zone access delegation permission.
|
||||
required: false
|
||||
type: bool
|
||||
aliases: ["managedby"]
|
||||
skip_overlap_check:
|
||||
description: |
|
||||
Force DNS zone creation even if it will overlap with an existing zone
|
||||
@@ -154,6 +159,7 @@ options:
|
||||
author:
|
||||
- Sergio Oliveira Campos (@seocam)
|
||||
- Thomas Woerner (@t-woerner)
|
||||
- Rafael Jeffman (@rjeffman)
|
||||
""" # noqa: E501
|
||||
|
||||
EXAMPLES = """
|
||||
@@ -253,6 +259,9 @@ class DNSZoneModule(IPAAnsibleModule):
|
||||
"idnsallowdynupdate": "dynamic_update",
|
||||
"idnssecinlinesigning": "dnssec",
|
||||
"idnsupdatepolicy": "update_policy",
|
||||
# FreeIPA uses 'managedby' for dnszone and dnsforwardzone
|
||||
# to manage 'permissions'.
|
||||
"managedby": "permission",
|
||||
# Mapping by method
|
||||
"idnsforwarders": self.get_ipa_idnsforwarders,
|
||||
"idnsallowtransfer": self.get_ipa_idnsallowtransfer,
|
||||
@@ -434,7 +443,7 @@ class DNSZoneModule(IPAAnsibleModule):
|
||||
is_zone_active = False
|
||||
else:
|
||||
zone = response["result"]
|
||||
# FreeIPA 4.9.10+ and 4.10 use proper mapping for boolean vaalues.
|
||||
# FreeIPA 4.9.10+ and 4.10 use proper mapping for boolean values.
|
||||
# See: https://github.com/freeipa/freeipa/pull/6294
|
||||
is_zone_active = (
|
||||
str(zone.get("idnszoneactive")[0]).upper() == "TRUE"
|
||||
@@ -462,18 +471,24 @@ class DNSZoneModule(IPAAnsibleModule):
|
||||
self.fail_json(
|
||||
msg="Either `name` or `name_from_ip` must be provided."
|
||||
)
|
||||
# check invalid parameters
|
||||
invalid = []
|
||||
if self.ipa_params.state != "present":
|
||||
invalid = ["name_from_ip"]
|
||||
|
||||
self.params_fail_used_invalid(invalid, self.ipa_params.state)
|
||||
invalid .extend(["name_from_ip"])
|
||||
if self.ipa_params.state == "absent":
|
||||
invalid.extend(["permission"])
|
||||
self.params_fail_used_invalid(invalid, self.ipa_params.state)
|
||||
|
||||
def define_ipa_commands(self):
|
||||
for zone_name in self.get_zone_names():
|
||||
# Look for existing zone in IPA
|
||||
zone, is_zone_active = self.get_zone(zone_name)
|
||||
args = self.ipa_params.get_ipa_command_args(zone=zone)
|
||||
|
||||
if self.ipa_params.state in ["present", "enabled", "disabled"]:
|
||||
args = self.ipa_params.get_ipa_command_args(zone=zone)
|
||||
# We'll handle "managedby" after dnszone add/mod.
|
||||
args.pop("managedby", None)
|
||||
|
||||
if not zone:
|
||||
# Since the zone doesn't exist we just create it
|
||||
# with given args
|
||||
@@ -487,6 +502,16 @@ class DNSZoneModule(IPAAnsibleModule):
|
||||
if not compare_args_ipa(self, args, zone):
|
||||
self.commands.append((zone_name, "dnszone_mod", args))
|
||||
|
||||
# Permissions must be set on existing zones.
|
||||
if self.ipa_params.permission is not None:
|
||||
is_managed = zone.get("managedby")
|
||||
if self.ipa_params.permission and not is_managed:
|
||||
self.commands.append(
|
||||
(zone_name, "dnszone_add_permission", {}))
|
||||
if not self.ipa_params.permission and is_managed:
|
||||
self.commands.append(
|
||||
(zone_name, "dnszone_remove_permission", {}))
|
||||
|
||||
if self.ipa_params.state == "enabled" and not is_zone_active:
|
||||
self.commands.append((zone_name, "dnszone_enable", {}))
|
||||
|
||||
@@ -555,6 +580,8 @@ def get_argument_spec():
|
||||
ttl=dict(type="int", required=False, default=None),
|
||||
default_ttl=dict(type="int", required=False, default=None),
|
||||
nsec3param_rec=dict(type="str", required=False, default=None),
|
||||
permission=dict(type="bool", required=False, default=None,
|
||||
aliases=["managedby"]),
|
||||
skip_nameserver_check=dict(type="bool", required=False, default=None),
|
||||
skip_overlap_check=dict(type="bool", required=False, default=None),
|
||||
)
|
||||
|
||||
@@ -123,6 +123,11 @@ options:
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
rename:
|
||||
description: Rename the group object
|
||||
required: false
|
||||
type: str
|
||||
aliases: ["new_name"]
|
||||
description:
|
||||
description: The group description
|
||||
type: str
|
||||
@@ -198,11 +203,16 @@ options:
|
||||
type: str
|
||||
default: group
|
||||
choices: ["member", "group"]
|
||||
rename:
|
||||
description: Rename the group object
|
||||
required: false
|
||||
type: str
|
||||
aliases: ["new_name"]
|
||||
state:
|
||||
description: State to ensure
|
||||
type: str
|
||||
default: present
|
||||
choices: ["present", "absent"]
|
||||
choices: ["present", "absent", "renamed"]
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
"""
|
||||
@@ -267,6 +277,13 @@ EXAMPLES = """
|
||||
group:
|
||||
- group2
|
||||
|
||||
# Rename a group
|
||||
- ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: oldname
|
||||
rename: newestname
|
||||
state: renamed
|
||||
|
||||
# Create a non-POSIX group
|
||||
- ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
@@ -380,18 +397,20 @@ def gen_member_args(user, group, service, externalmember, idoverrideuser):
|
||||
|
||||
|
||||
def check_parameters(module, state, action):
|
||||
invalid = []
|
||||
if state == "present":
|
||||
if action == "member":
|
||||
invalid = ["description", "gid", "posix", "nonposix", "external",
|
||||
"nomembers"]
|
||||
|
||||
else:
|
||||
invalid = ["description", "gid", "posix", "nonposix", "external",
|
||||
"nomembers"]
|
||||
if action == "group":
|
||||
invalid = ["description", "gid", "posix", "nonposix", "external",
|
||||
"nomembers"]
|
||||
if action == "group":
|
||||
if state == "present":
|
||||
invalid = []
|
||||
elif state == "absent":
|
||||
invalid.extend(["user", "group", "service", "externalmember"])
|
||||
|
||||
if state == "renamed":
|
||||
if action == "member":
|
||||
module.fail_json(
|
||||
msg="Action member can not be used with state: renamed.")
|
||||
invalid.extend(["user", "group", "service", "externalmember"])
|
||||
else:
|
||||
invalid.append("rename")
|
||||
module.params_fail_used_invalid(invalid, state, action)
|
||||
|
||||
|
||||
@@ -448,7 +467,9 @@ def main():
|
||||
aliases=[
|
||||
"ipaexternalmember",
|
||||
"external_member"
|
||||
])
|
||||
]),
|
||||
rename=dict(type="str", required=False, default=None,
|
||||
aliases=["new_name"]),
|
||||
)
|
||||
ansible_module = IPAAnsibleModule(
|
||||
argument_spec=dict(
|
||||
@@ -470,7 +491,7 @@ def main():
|
||||
action=dict(type="str", default="group",
|
||||
choices=["member", "group"]),
|
||||
state=dict(type="str", default="present",
|
||||
choices=["present", "absent"]),
|
||||
choices=["present", "absent", "renamed"]),
|
||||
|
||||
# Add group specific parameters for simple use case
|
||||
**group_spec
|
||||
@@ -499,15 +520,19 @@ def main():
|
||||
idoverrideuser = ansible_module.params_get("idoverrideuser")
|
||||
posix = ansible_module.params_get("posix")
|
||||
nomembers = ansible_module.params_get("nomembers")
|
||||
user = ansible_module.params_get("user")
|
||||
group = ansible_module.params_get("group")
|
||||
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("membermanager_user")
|
||||
membermanager_group = ansible_module.params_get("membermanager_group")
|
||||
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
|
||||
state = ansible_module.params_get("state")
|
||||
|
||||
# Check parameters
|
||||
@@ -516,10 +541,11 @@ def main():
|
||||
(groups is None or len(groups) < 1):
|
||||
ansible_module.fail_json(msg="At least one name or groups is required")
|
||||
|
||||
if state == "present":
|
||||
if state in ["present", "renamed"]:
|
||||
if names is not None and len(names) != 1:
|
||||
what = "renamed" if state == "renamed" else "added"
|
||||
ansible_module.fail_json(
|
||||
msg="Only one group can be added at a time using 'name'.")
|
||||
msg="Only one group can be %s at a time using 'name'." % what)
|
||||
|
||||
check_parameters(ansible_module, state, action)
|
||||
|
||||
@@ -633,10 +659,15 @@ def main():
|
||||
membermanager_group = group_name.get("membermanager_group")
|
||||
externalmember = group_name.get("externalmember")
|
||||
nomembers = group_name.get("nomembers")
|
||||
rename = group_name.get("rename")
|
||||
|
||||
check_parameters(ansible_module, state, action)
|
||||
|
||||
elif isinstance(group_name, (str, unicode)):
|
||||
elif (
|
||||
isinstance(
|
||||
group_name, (str, unicode) # pylint: disable=W0012,E0606
|
||||
)
|
||||
):
|
||||
name = group_name
|
||||
else:
|
||||
ansible_module.fail_json(msg="Group '%s' is not valid" %
|
||||
@@ -794,6 +825,11 @@ def main():
|
||||
membermanager_group,
|
||||
res_find.get("membermanager_group")
|
||||
)
|
||||
elif state == "renamed":
|
||||
if res_find is None:
|
||||
ansible_module.fail_json(msg="No group '%s'" % name)
|
||||
elif rename != name:
|
||||
commands.append([name, 'group_mod', {"rename": rename}])
|
||||
else:
|
||||
ansible_module.fail_json(msg="Unkown state '%s'" % state)
|
||||
|
||||
@@ -868,7 +904,7 @@ def main():
|
||||
|
||||
# Execute commands
|
||||
changed = ansible_module.execute_ipa_commands(
|
||||
commands, fail_on_member_errors=True)
|
||||
commands, batch=True, keeponly=[], fail_on_member_errors=True)
|
||||
|
||||
# Done
|
||||
|
||||
|
||||
@@ -188,13 +188,12 @@ def find_hbacrule(module, name):
|
||||
elif len(_result["result"]) == 1:
|
||||
res = _result["result"][0]
|
||||
# hbacsvcgroup names are converted to lower case while creation with
|
||||
# hbacsvcgroup_add.
|
||||
# The hbacsvcgroup for sudo is builtin with the name "Sudo" though.
|
||||
# This breaks the lower case comparison. Therefore all
|
||||
# memberservice_hbacsvcgroup items are converted to lower case if
|
||||
# "Sudo" is in the list.
|
||||
# hbacsvcgroup_add, but builtin names may have mixed case as "Sudo",
|
||||
# breaking the lower case comparison. Therefore all
|
||||
# memberservice_hbacsvcgroup items are converted to lower case.
|
||||
# (See: https://pagure.io/freeipa/issue/9464).
|
||||
_member = "memberservice_hbacsvcgroup"
|
||||
if _member in res and "Sudo" in res[_member]:
|
||||
if _member in res:
|
||||
res[_member] = [item.lower() for item in res[_member]]
|
||||
return res
|
||||
|
||||
@@ -400,7 +399,8 @@ def main():
|
||||
|
||||
if hbacsvc is not None:
|
||||
hbacsvc_add, hbacsvc_del = gen_add_del_lists(
|
||||
hbacsvc, res_find.get("memberservice_hbacsvc"))
|
||||
hbacsvc, res_find.get("memberservice_hbacsvc"),
|
||||
)
|
||||
|
||||
if hbacsvcgroup is not None:
|
||||
hbacsvcgroup_add, hbacsvcgroup_del = gen_add_del_lists(
|
||||
|
||||
@@ -509,7 +509,9 @@ host:
|
||||
|
||||
from ansible.module_utils.ansible_freeipa_module import \
|
||||
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, \
|
||||
encode_certificate, is_ipv4_addr, is_ipv6_addr, ipalib_errors
|
||||
encode_certificate, is_ipv4_addr, is_ipv6_addr, ipalib_errors, \
|
||||
gen_add_list, gen_intersection_list, normalize_sshpubkey, \
|
||||
convert_input_certificates
|
||||
from ansible.module_utils import six
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
@@ -533,6 +535,11 @@ def find_host(module, name):
|
||||
if certs is not None:
|
||||
_res["usercertificate"] = [encode_certificate(cert) for
|
||||
cert in certs]
|
||||
# krbprincipalname is returned as ipapython.kerberos.Principal, convert
|
||||
# to string
|
||||
principals = _res.get("krbprincipalname")
|
||||
if principals is not None:
|
||||
_res["krbprincipalname"] = [str(princ) for princ in principals]
|
||||
return _res
|
||||
|
||||
|
||||
@@ -677,7 +684,7 @@ def check_authind(module, auth_ind):
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def result_handler(module, result, command, name, args, errors, exit_args,
|
||||
def result_handler(module, result, command, name, args, exit_args,
|
||||
single_host):
|
||||
if "random" in args and command in ["host_add", "host_mod"] \
|
||||
and "randompassword" in result["result"]:
|
||||
@@ -688,41 +695,6 @@ def result_handler(module, result, command, name, args, errors, exit_args,
|
||||
exit_args.setdefault(name, {})["randompassword"] = \
|
||||
result["result"]["randompassword"]
|
||||
|
||||
# All "already a member" and "not a member" failures in the
|
||||
# result are ignored. All others are reported.
|
||||
if "failed" in result and len(result["failed"]) > 0:
|
||||
for item in result["failed"]:
|
||||
failed_item = result["failed"][item]
|
||||
for member_type in failed_item:
|
||||
for member, failure in failed_item[member_type]:
|
||||
if "already a member" in failure \
|
||||
or "not a member" in failure:
|
||||
continue
|
||||
errors.append("%s: %s %s: %s" % (
|
||||
command, member_type, member, failure))
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def exception_handler(module, ex, errors, exit_args, single_host):
|
||||
msg = str(ex)
|
||||
if "already contains" in msg \
|
||||
or "does not contain" in msg:
|
||||
return True
|
||||
|
||||
# The canonical principal name may not be removed
|
||||
if "equal to the canonical principal name must" in msg:
|
||||
return True
|
||||
|
||||
# Host is already disabled, ignore error
|
||||
if "This entry is already disabled" in msg:
|
||||
return True
|
||||
|
||||
# Ignore no modification error.
|
||||
if "no modifications to be performed" in msg:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
host_spec = dict(
|
||||
@@ -876,10 +848,11 @@ def main():
|
||||
allow_retrieve_keytab_hostgroup = ansible_module.params_get(
|
||||
"allow_retrieve_keytab_hostgroup")
|
||||
mac_address = ansible_module.params_get("mac_address")
|
||||
sshpubkey = ansible_module.params_get("sshpubkey",
|
||||
allow_empty_string=True)
|
||||
sshpubkey = ansible_module.params_get(
|
||||
"sshpubkey", allow_empty_list_item=True)
|
||||
userclass = ansible_module.params_get("userclass")
|
||||
auth_ind = ansible_module.params_get("auth_ind", allow_empty_string=True)
|
||||
auth_ind = ansible_module.params_get(
|
||||
"auth_ind", allow_empty_list_item=True)
|
||||
requires_pre_auth = ansible_module.params_get("requires_pre_auth")
|
||||
ok_as_delegate = ansible_module.params_get("ok_as_delegate")
|
||||
ok_to_auth_as_delegate = ansible_module.params_get(
|
||||
@@ -915,6 +888,12 @@ def main():
|
||||
auth_ind, requires_pre_auth, ok_as_delegate, ok_to_auth_as_delegate,
|
||||
force, reverse, ip_address, update_dns, update_password)
|
||||
|
||||
certificate = convert_input_certificates(ansible_module, certificate,
|
||||
state)
|
||||
|
||||
if sshpubkey is not None:
|
||||
sshpubkey = [str(normalize_sshpubkey(key)) for key in sshpubkey]
|
||||
|
||||
# Use hosts if names is None
|
||||
if hosts is not None:
|
||||
names = hosts
|
||||
@@ -998,7 +977,16 @@ def main():
|
||||
ok_to_auth_as_delegate, force, reverse, ip_address,
|
||||
update_dns, update_password)
|
||||
|
||||
elif isinstance(host, (str, unicode)):
|
||||
certificate = convert_input_certificates(ansible_module,
|
||||
certificate, state)
|
||||
|
||||
if sshpubkey is not None:
|
||||
sshpubkey = [str(normalize_sshpubkey(key)) for
|
||||
key in sshpubkey]
|
||||
|
||||
elif (
|
||||
isinstance(host, (str, unicode)) # pylint: disable=W0012,E0606
|
||||
):
|
||||
name = host
|
||||
else:
|
||||
ansible_module.fail_json(msg="Host '%s' is not valid" %
|
||||
@@ -1073,6 +1061,17 @@ def main():
|
||||
args["krbprincipalauthind"] == ['']:
|
||||
del args["krbprincipalauthind"]
|
||||
|
||||
# Ignore sshpubkey if it is empty (for resetting)
|
||||
# and not set for the host
|
||||
if "ipasshpubkey" not in res_find and \
|
||||
"ipasshpubkey" in args and \
|
||||
args["ipasshpubkey"] == []:
|
||||
del args["ipasshpubkey"]
|
||||
|
||||
# Ignore updatedns if it is the only arg
|
||||
if "updatedns" in args and len(args) == 1:
|
||||
del args["updatedns"]
|
||||
|
||||
# For all settings is args, check if there are
|
||||
# different settings in the find result.
|
||||
# If yes: modify
|
||||
@@ -1105,7 +1104,7 @@ def main():
|
||||
gen_add_del_lists(managedby_host,
|
||||
res_find.get("managedby_host"))
|
||||
principal_add, principal_del = gen_add_del_lists(
|
||||
principal, res_find.get("principal"))
|
||||
principal, res_find.get("krbprincipalname"))
|
||||
# Principals are not returned as utf8 for IPA using
|
||||
# python2 using host_show, therefore we need to
|
||||
# convert the principals that we should remove.
|
||||
@@ -1173,50 +1172,115 @@ def main():
|
||||
gen_add_del_lists(
|
||||
dnsrecord_args.get("aaaarecord"),
|
||||
_dnsrec.get("aaaarecord"))
|
||||
else:
|
||||
certificate_add = certificate or []
|
||||
certificate_del = []
|
||||
managedby_host_add = managedby_host or []
|
||||
managedby_host_del = []
|
||||
principal_add = principal or []
|
||||
principal_del = []
|
||||
allow_create_keytab_user_add = \
|
||||
allow_create_keytab_user or []
|
||||
allow_create_keytab_user_del = []
|
||||
allow_create_keytab_group_add = \
|
||||
allow_create_keytab_group or []
|
||||
allow_create_keytab_group_del = []
|
||||
allow_create_keytab_host_add = \
|
||||
allow_create_keytab_host or []
|
||||
allow_create_keytab_host_del = []
|
||||
allow_create_keytab_hostgroup_add = \
|
||||
allow_create_keytab_hostgroup or []
|
||||
allow_create_keytab_hostgroup_del = []
|
||||
allow_retrieve_keytab_user_add = \
|
||||
allow_retrieve_keytab_user or []
|
||||
allow_retrieve_keytab_user_del = []
|
||||
allow_retrieve_keytab_group_add = \
|
||||
allow_retrieve_keytab_group or []
|
||||
allow_retrieve_keytab_group_del = []
|
||||
allow_retrieve_keytab_host_add = \
|
||||
allow_retrieve_keytab_host or []
|
||||
allow_retrieve_keytab_host_del = []
|
||||
allow_retrieve_keytab_hostgroup_add = \
|
||||
allow_retrieve_keytab_hostgroup or []
|
||||
allow_retrieve_keytab_hostgroup_del = []
|
||||
_dnsrec = res_find_dnsrecord or {}
|
||||
dnsrecord_a_add = gen_add_list(
|
||||
dnsrecord_args.get("arecord"),
|
||||
_dnsrec.get("arecord"))
|
||||
dnsrecord_a_del = []
|
||||
dnsrecord_aaaa_add = gen_add_list(
|
||||
dnsrecord_args.get("aaaarecord"),
|
||||
_dnsrec.get("aaaarecord"))
|
||||
dnsrecord_aaaa_del = []
|
||||
|
||||
else:
|
||||
# action member
|
||||
if res_find is None:
|
||||
ansible_module.fail_json(
|
||||
msg="No host '%s'" % name)
|
||||
|
||||
if action != "host" or (action == "host" and res_find is None):
|
||||
certificate_add = certificate or []
|
||||
certificate_add = gen_add_list(
|
||||
certificate, res_find.get("usercertificate"))
|
||||
certificate_del = []
|
||||
managedby_host_add = managedby_host or []
|
||||
managedby_host_add = gen_add_list(
|
||||
managedby_host, res_find.get("managedby_host"))
|
||||
managedby_host_del = []
|
||||
principal_add = principal or []
|
||||
principal_add = gen_add_list(
|
||||
principal, res_find.get("krbprincipalname"))
|
||||
principal_del = []
|
||||
allow_create_keytab_user_add = \
|
||||
allow_create_keytab_user or []
|
||||
allow_create_keytab_user_add = gen_add_list(
|
||||
allow_create_keytab_user,
|
||||
res_find.get(
|
||||
"ipaallowedtoperform_write_keys_user"))
|
||||
allow_create_keytab_user_del = []
|
||||
allow_create_keytab_group_add = \
|
||||
allow_create_keytab_group or []
|
||||
allow_create_keytab_group_add = gen_add_list(
|
||||
allow_create_keytab_group,
|
||||
res_find.get(
|
||||
"ipaallowedtoperform_write_keys_group"))
|
||||
allow_create_keytab_group_del = []
|
||||
allow_create_keytab_host_add = \
|
||||
allow_create_keytab_host or []
|
||||
allow_create_keytab_host_add = gen_add_list(
|
||||
allow_create_keytab_host,
|
||||
res_find.get(
|
||||
"ipaallowedtoperform_write_keys_host"))
|
||||
allow_create_keytab_host_del = []
|
||||
allow_create_keytab_hostgroup_add = \
|
||||
allow_create_keytab_hostgroup or []
|
||||
allow_create_keytab_hostgroup_add = gen_add_list(
|
||||
allow_create_keytab_hostgroup,
|
||||
res_find.get(
|
||||
"ipaallowedtoperform_write_keys_hostgroup"))
|
||||
allow_create_keytab_hostgroup_del = []
|
||||
allow_retrieve_keytab_user_add = \
|
||||
allow_retrieve_keytab_user or []
|
||||
allow_retrieve_keytab_user_add = gen_add_list(
|
||||
allow_retrieve_keytab_user,
|
||||
res_find.get(
|
||||
"ipaallowedtoperform_read_keys_user"))
|
||||
allow_retrieve_keytab_user_del = []
|
||||
allow_retrieve_keytab_group_add = \
|
||||
allow_retrieve_keytab_group or []
|
||||
allow_retrieve_keytab_group_add = gen_add_list(
|
||||
allow_retrieve_keytab_group,
|
||||
res_find.get(
|
||||
"ipaallowedtoperform_read_keys_group"))
|
||||
allow_retrieve_keytab_group_del = []
|
||||
allow_retrieve_keytab_host_add = \
|
||||
allow_retrieve_keytab_host or []
|
||||
allow_retrieve_keytab_host_add = gen_add_list(
|
||||
allow_retrieve_keytab_host,
|
||||
res_find.get(
|
||||
"ipaallowedtoperform_read_keys_host"))
|
||||
allow_retrieve_keytab_host_del = []
|
||||
allow_retrieve_keytab_hostgroup_add = \
|
||||
allow_retrieve_keytab_hostgroup or []
|
||||
allow_retrieve_keytab_hostgroup_add = gen_add_list(
|
||||
allow_retrieve_keytab_hostgroup,
|
||||
res_find.get(
|
||||
"ipaallowedtoperform_read_keys_hostgroup"))
|
||||
allow_retrieve_keytab_hostgroup_del = []
|
||||
dnsrecord_a_add = dnsrecord_args.get("arecord") or []
|
||||
_dnsrec = res_find_dnsrecord or {}
|
||||
dnsrecord_a_add = gen_add_list(
|
||||
dnsrecord_args.get("arecord"),
|
||||
_dnsrec.get("arecord"))
|
||||
dnsrecord_a_del = []
|
||||
dnsrecord_aaaa_add = dnsrecord_args.get("aaaarecord") or []
|
||||
dnsrecord_aaaa_add = gen_add_list(
|
||||
dnsrecord_args.get("aaaarecord"),
|
||||
_dnsrec.get("aaaarecord"))
|
||||
dnsrecord_aaaa_del = []
|
||||
|
||||
# Remove canonical principal from principal_del
|
||||
canonical_principal = "host/" + name + "@" + server_realm
|
||||
# canonical_principal is also in find_res["krbcanonicalname"]
|
||||
if canonical_principal in principal_del and \
|
||||
action == "host" and (principal is not None or
|
||||
canonical_principal not in principal):
|
||||
@@ -1397,8 +1461,10 @@ def main():
|
||||
# the removal of non-existing entries.
|
||||
|
||||
# Remove certificates
|
||||
if certificate is not None:
|
||||
for _certificate in certificate:
|
||||
certificate_del = gen_intersection_list(
|
||||
certificate, res_find.get("usercertificate"))
|
||||
if certificate_del is not None:
|
||||
for _certificate in certificate_del:
|
||||
commands.append([name, "host_remove_cert",
|
||||
{
|
||||
"usercertificate":
|
||||
@@ -1411,8 +1477,10 @@ def main():
|
||||
# the removal of non-existing entries.
|
||||
|
||||
# Remove managedby_hosts
|
||||
if managedby_host is not None:
|
||||
for _managedby_host in managedby_host:
|
||||
managedby_host_del = gen_intersection_list(
|
||||
managedby_host, res_find.get("managedby_host"))
|
||||
if managedby_host_del is not None:
|
||||
for _managedby_host in managedby_host_del:
|
||||
commands.append([name, "host_remove_managedby",
|
||||
{
|
||||
"host":
|
||||
@@ -1425,8 +1493,10 @@ def main():
|
||||
# the removal of non-existing entries.
|
||||
|
||||
# Remove principals
|
||||
if principal is not None:
|
||||
for _principal in principal:
|
||||
principal_del = gen_intersection_list(
|
||||
principal, res_find.get("krbprincipalname"))
|
||||
if principal_del is not None:
|
||||
for _principal in principal_del:
|
||||
commands.append([name, "host_remove_principal",
|
||||
{
|
||||
"krbprincipalname":
|
||||
@@ -1434,60 +1504,86 @@ def main():
|
||||
}])
|
||||
|
||||
# Disallow create keytab
|
||||
if allow_create_keytab_user is not None or \
|
||||
allow_create_keytab_group is not None or \
|
||||
allow_create_keytab_host is not None or \
|
||||
allow_create_keytab_hostgroup is not None:
|
||||
allow_create_keytab_user_del = gen_intersection_list(
|
||||
allow_create_keytab_user,
|
||||
res_find.get("ipaallowedtoperform_write_keys_user"))
|
||||
allow_create_keytab_group_del = gen_intersection_list(
|
||||
allow_create_keytab_group,
|
||||
res_find.get("ipaallowedtoperform_write_keys_group"))
|
||||
allow_create_keytab_host_del = gen_intersection_list(
|
||||
allow_create_keytab_host,
|
||||
res_find.get("ipaallowedtoperform_write_keys_host"))
|
||||
allow_create_keytab_hostgroup_del = gen_intersection_list(
|
||||
allow_create_keytab_hostgroup,
|
||||
res_find.get(
|
||||
"ipaallowedtoperform_write_keys_hostgroup"))
|
||||
if len(allow_create_keytab_user_del) > 0 or \
|
||||
len(allow_create_keytab_group_del) > 0 or \
|
||||
len(allow_create_keytab_host_del) > 0 or \
|
||||
len(allow_create_keytab_hostgroup_del) > 0:
|
||||
commands.append(
|
||||
[name, "host_disallow_create_keytab",
|
||||
{
|
||||
"user": allow_create_keytab_user,
|
||||
"group": allow_create_keytab_group,
|
||||
"host": allow_create_keytab_host,
|
||||
"hostgroup": allow_create_keytab_hostgroup,
|
||||
"user": allow_create_keytab_user_del,
|
||||
"group": allow_create_keytab_group_del,
|
||||
"host": allow_create_keytab_host_del,
|
||||
"hostgroup":
|
||||
allow_create_keytab_hostgroup_del,
|
||||
}])
|
||||
|
||||
# Disallow retrieve keytab
|
||||
if allow_retrieve_keytab_user is not None or \
|
||||
allow_retrieve_keytab_group is not None or \
|
||||
allow_retrieve_keytab_host is not None or \
|
||||
allow_retrieve_keytab_hostgroup is not None:
|
||||
allow_retrieve_keytab_user_del = gen_intersection_list(
|
||||
allow_retrieve_keytab_user,
|
||||
res_find.get("ipaallowedtoperform_read_keys_user"))
|
||||
allow_retrieve_keytab_group_del = gen_intersection_list(
|
||||
allow_retrieve_keytab_group,
|
||||
res_find.get("ipaallowedtoperform_read_keys_group"))
|
||||
allow_retrieve_keytab_host_del = gen_intersection_list(
|
||||
allow_retrieve_keytab_host,
|
||||
res_find.get("ipaallowedtoperform_read_keys_host"))
|
||||
allow_retrieve_keytab_hostgroup_del = \
|
||||
gen_intersection_list(
|
||||
allow_retrieve_keytab_hostgroup,
|
||||
res_find.get(
|
||||
"ipaallowedtoperform_read_keys_hostgroup"))
|
||||
if len(allow_retrieve_keytab_user_del) > 0 or \
|
||||
len(allow_retrieve_keytab_group_del) > 0 or \
|
||||
len(allow_retrieve_keytab_host_del) > 0 or \
|
||||
len(allow_retrieve_keytab_hostgroup_del) > 0:
|
||||
commands.append(
|
||||
[name, "host_disallow_retrieve_keytab",
|
||||
{
|
||||
"user": allow_retrieve_keytab_user,
|
||||
"group": allow_retrieve_keytab_group,
|
||||
"host": allow_retrieve_keytab_host,
|
||||
"hostgroup": allow_retrieve_keytab_hostgroup,
|
||||
"user": allow_retrieve_keytab_user_del,
|
||||
"group": allow_retrieve_keytab_group_del,
|
||||
"host": allow_retrieve_keytab_host_del,
|
||||
"hostgroup":
|
||||
allow_retrieve_keytab_hostgroup_del,
|
||||
}])
|
||||
|
||||
dnsrecord_args = gen_dnsrecord_args(ansible_module,
|
||||
ip_address, reverse)
|
||||
if res_find_dnsrecord is not None:
|
||||
dnsrecord_args = gen_dnsrecord_args(
|
||||
ansible_module, ip_address, reverse)
|
||||
|
||||
# Remove arecord and aaaarecord from dnsrecord_args
|
||||
# if the record does not exits in res_find_dnsrecord
|
||||
# to prevent "DNS resource record not found" error
|
||||
if "arecord" in dnsrecord_args \
|
||||
and dnsrecord_args["arecord"] is not None \
|
||||
and len(dnsrecord_args["arecord"]) > 0 \
|
||||
and (res_find_dnsrecord is None
|
||||
or "arecord" not in res_find_dnsrecord):
|
||||
del dnsrecord_args["arecord"]
|
||||
if "aaaarecord" in dnsrecord_args \
|
||||
and dnsrecord_args["aaaarecord"] is not None \
|
||||
and len(dnsrecord_args["aaaarecord"]) > 0 \
|
||||
and (res_find_dnsrecord is None
|
||||
or "aaaarecord" not in res_find_dnsrecord):
|
||||
del dnsrecord_args["aaaarecord"]
|
||||
# Only keep a and aaaa recrords that are part
|
||||
# of res_find_dnsrecord.
|
||||
for _type in ["arecord", "aaaarecord"]:
|
||||
if _type in dnsrecord_args:
|
||||
recs = gen_intersection_list(
|
||||
dnsrecord_args[_type],
|
||||
res_find_dnsrecord.get(_type))
|
||||
if len(recs) > 0:
|
||||
dnsrecord_args[_type] = recs
|
||||
else:
|
||||
del dnsrecord_args[_type]
|
||||
|
||||
if "arecord" in dnsrecord_args or \
|
||||
"aaaarecord" in dnsrecord_args:
|
||||
domain_name = name[name.find(".") + 1:]
|
||||
host_name = name[:name.find(".")]
|
||||
dnsrecord_args["idnsname"] = host_name
|
||||
if "arecord" in dnsrecord_args or \
|
||||
"aaaarecord" in dnsrecord_args:
|
||||
domain_name = name[name.find(".") + 1:]
|
||||
host_name = name[:name.find(".")]
|
||||
dnsrecord_args["idnsname"] = host_name
|
||||
|
||||
commands.append([domain_name, "dnsrecord_del",
|
||||
dnsrecord_args])
|
||||
commands.append([domain_name, "dnsrecord_del",
|
||||
dnsrecord_args])
|
||||
|
||||
elif state == "disabled":
|
||||
if res_find is not None:
|
||||
@@ -1503,7 +1599,7 @@ def main():
|
||||
# Execute commands
|
||||
|
||||
changed = ansible_module.execute_ipa_commands(
|
||||
commands, result_handler, exception_handler,
|
||||
commands, result_handler, batch=True, keeponly=["randompassword"],
|
||||
exit_args=exit_args, single_host=hosts is None)
|
||||
|
||||
# Done
|
||||
|
||||
@@ -181,16 +181,6 @@ def gen_args(description, nomembers, rename):
|
||||
return _args
|
||||
|
||||
|
||||
def gen_member_args(host, hostgroup):
|
||||
_args = {}
|
||||
if host is not None:
|
||||
_args["member_host"] = host
|
||||
if hostgroup is not None:
|
||||
_args["member_hostgroup"] = hostgroup
|
||||
|
||||
return _args
|
||||
|
||||
|
||||
def main():
|
||||
ansible_module = IPAAnsibleModule(
|
||||
argument_spec=dict(
|
||||
@@ -224,16 +214,20 @@ def main():
|
||||
# Get parameters
|
||||
|
||||
# general
|
||||
names = ansible_module.params_get("name")
|
||||
names = ansible_module.params_get_lowercase("name")
|
||||
|
||||
# present
|
||||
description = ansible_module.params_get("description")
|
||||
nomembers = ansible_module.params_get("nomembers")
|
||||
host = ansible_module.params_get("host")
|
||||
hostgroup = ansible_module.params_get("hostgroup")
|
||||
membermanager_user = ansible_module.params_get("membermanager_user")
|
||||
membermanager_group = ansible_module.params_get("membermanager_group")
|
||||
rename = ansible_module.params_get("rename")
|
||||
hostgroup = ansible_module.params_get_lowercase("hostgroup")
|
||||
membermanager_user = ansible_module.params_get_lowercase(
|
||||
"membermanager_user"
|
||||
)
|
||||
membermanager_group = ansible_module.params_get_lowercase(
|
||||
"membermanager_group"
|
||||
)
|
||||
rename = ansible_module.params_get_lowercase("rename")
|
||||
action = ansible_module.params_get("action")
|
||||
# state
|
||||
state = ansible_module.params_get("state")
|
||||
@@ -306,6 +300,12 @@ def main():
|
||||
commands = []
|
||||
|
||||
for name in names:
|
||||
# clean add/del lists
|
||||
host_add, host_del = [], []
|
||||
hostgroup_add, hostgroup_del = [], []
|
||||
membermanager_user_add, membermanager_user_del = [], []
|
||||
membermanager_group_add, membermanager_group_del = [], []
|
||||
|
||||
# Make sure hostgroup exists
|
||||
res_find = find_hostgroup(ansible_module, name)
|
||||
|
||||
@@ -328,63 +328,26 @@ def main():
|
||||
# Set res_find to empty dict for next step
|
||||
res_find = {}
|
||||
|
||||
member_args = gen_member_args(host, hostgroup)
|
||||
if not compare_args_ipa(ansible_module, member_args,
|
||||
res_find):
|
||||
# Generate addition and removal lists
|
||||
host_add, host_del = gen_add_del_lists(
|
||||
host, res_find.get("member_host"))
|
||||
# Generate addition and removal lists
|
||||
host_add, host_del = gen_add_del_lists(
|
||||
host, res_find.get("member_host")
|
||||
)
|
||||
|
||||
hostgroup_add, hostgroup_del = gen_add_del_lists(
|
||||
hostgroup, res_find.get("member_hostgroup"))
|
||||
|
||||
# Add members
|
||||
if len(host_add) > 0 or len(hostgroup_add) > 0:
|
||||
commands.append([name, "hostgroup_add_member",
|
||||
{
|
||||
"host": host_add,
|
||||
"hostgroup": hostgroup_add,
|
||||
}])
|
||||
# Remove members
|
||||
if len(host_del) > 0 or len(hostgroup_del) > 0:
|
||||
commands.append([name, "hostgroup_remove_member",
|
||||
{
|
||||
"host": host_del,
|
||||
"hostgroup": hostgroup_del,
|
||||
}])
|
||||
|
||||
membermanager_user_add, membermanager_user_del = \
|
||||
gen_add_del_lists(
|
||||
membermanager_user,
|
||||
res_find.get("membermanager_user")
|
||||
)
|
||||
|
||||
membermanager_group_add, membermanager_group_del = \
|
||||
gen_add_del_lists(
|
||||
membermanager_group,
|
||||
res_find.get("membermanager_group")
|
||||
)
|
||||
hostgroup_add, hostgroup_del = gen_add_del_lists(
|
||||
hostgroup, res_find.get("member_hostgroup")
|
||||
)
|
||||
|
||||
if has_add_membermanager:
|
||||
# Add membermanager users and groups
|
||||
if len(membermanager_user_add) > 0 or \
|
||||
len(membermanager_group_add) > 0:
|
||||
commands.append(
|
||||
[name, "hostgroup_add_member_manager",
|
||||
{
|
||||
"user": membermanager_user_add,
|
||||
"group": membermanager_group_add,
|
||||
}]
|
||||
membermanager_user_add, membermanager_user_del = \
|
||||
gen_add_del_lists(
|
||||
membermanager_user,
|
||||
res_find.get("membermanager_user")
|
||||
)
|
||||
# Remove member manager
|
||||
if len(membermanager_user_del) > 0 or \
|
||||
len(membermanager_group_del) > 0:
|
||||
commands.append(
|
||||
[name, "hostgroup_remove_member_manager",
|
||||
{
|
||||
"user": membermanager_user_del,
|
||||
"group": membermanager_group_del,
|
||||
}]
|
||||
|
||||
membermanager_group_add, membermanager_group_del = \
|
||||
gen_add_del_lists(
|
||||
membermanager_group,
|
||||
res_find.get("membermanager_group")
|
||||
)
|
||||
|
||||
elif action == "member":
|
||||
@@ -394,45 +357,25 @@ def main():
|
||||
|
||||
# Reduce add lists for member_host and member_hostgroup,
|
||||
# to new entries only that are not in res_find.
|
||||
if host is not None and "member_host" in res_find:
|
||||
host = gen_add_list(host, res_find["member_host"])
|
||||
if hostgroup is not None \
|
||||
and "member_hostgroup" in res_find:
|
||||
hostgroup = gen_add_list(
|
||||
hostgroup, res_find["member_hostgroup"])
|
||||
|
||||
# Ensure members are present
|
||||
commands.append([name, "hostgroup_add_member",
|
||||
{
|
||||
"host": host,
|
||||
"hostgroup": hostgroup,
|
||||
}])
|
||||
host_add = gen_add_list(
|
||||
host, res_find.get("member_host")
|
||||
)
|
||||
hostgroup_add = gen_add_list(
|
||||
hostgroup, res_find.get("member_hostgroup")
|
||||
)
|
||||
|
||||
if has_add_membermanager:
|
||||
# Reduce add list for membermanager_user and
|
||||
# membermanager_group to new entries only that are
|
||||
# not in res_find.
|
||||
if membermanager_user is not None \
|
||||
and "membermanager_user" in res_find:
|
||||
membermanager_user = gen_add_list(
|
||||
membermanager_user,
|
||||
res_find["membermanager_user"])
|
||||
if membermanager_group is not None \
|
||||
and "membermanager_group" in res_find:
|
||||
membermanager_group = gen_add_list(
|
||||
membermanager_group,
|
||||
res_find["membermanager_group"])
|
||||
|
||||
# Add membermanager users and groups
|
||||
if membermanager_user is not None or \
|
||||
membermanager_group is not None:
|
||||
commands.append(
|
||||
[name, "hostgroup_add_member_manager",
|
||||
{
|
||||
"user": membermanager_user,
|
||||
"group": membermanager_group,
|
||||
}]
|
||||
)
|
||||
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 == "renamed":
|
||||
if res_find is not None:
|
||||
@@ -463,46 +406,72 @@ def main():
|
||||
# Reduce del lists of member_host and member_hostgroup,
|
||||
# to the entries only that are in res_find.
|
||||
if host is not None:
|
||||
host = gen_intersection_list(
|
||||
host, res_find.get("member_host"))
|
||||
host_del = gen_intersection_list(
|
||||
host, res_find.get("member_host")
|
||||
)
|
||||
if hostgroup is not None:
|
||||
hostgroup = gen_intersection_list(
|
||||
hostgroup, res_find.get("member_hostgroup"))
|
||||
|
||||
# Ensure members are absent
|
||||
commands.append([name, "hostgroup_remove_member",
|
||||
{
|
||||
"host": host,
|
||||
"hostgroup": hostgroup,
|
||||
}])
|
||||
hostgroup_del = gen_intersection_list(
|
||||
hostgroup, res_find.get("member_hostgroup")
|
||||
)
|
||||
|
||||
if has_add_membermanager:
|
||||
# Reduce del lists of membermanager_user and
|
||||
# membermanager_group to the entries only that are
|
||||
# in res_find.
|
||||
if membermanager_user is not None:
|
||||
membermanager_user = gen_intersection_list(
|
||||
membermanager_user,
|
||||
res_find.get("membermanager_user"))
|
||||
if membermanager_group is not None:
|
||||
membermanager_group = gen_intersection_list(
|
||||
membermanager_group,
|
||||
res_find.get("membermanager_group"))
|
||||
|
||||
# Remove membermanager users and groups
|
||||
if membermanager_user is not None or \
|
||||
membermanager_group is not None:
|
||||
commands.append(
|
||||
[name, "hostgroup_remove_member_manager",
|
||||
{
|
||||
"user": membermanager_user,
|
||||
"group": membermanager_group,
|
||||
}]
|
||||
)
|
||||
# Get lists of membermanager users that exist
|
||||
# in IPA and should be removed.
|
||||
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")
|
||||
)
|
||||
|
||||
else:
|
||||
ansible_module.fail_json(msg="Unkown state '%s'" % state)
|
||||
|
||||
# Manage members
|
||||
|
||||
# Add members
|
||||
if host_add or hostgroup_add:
|
||||
commands.append([
|
||||
name, "hostgroup_add_member",
|
||||
{
|
||||
"host": host_add,
|
||||
"hostgroup": hostgroup_add,
|
||||
}
|
||||
])
|
||||
|
||||
# Remove members
|
||||
if host_del or hostgroup_del:
|
||||
commands.append([
|
||||
name, "hostgroup_remove_member",
|
||||
{
|
||||
"host": host_del,
|
||||
"hostgroup": hostgroup_del,
|
||||
}
|
||||
])
|
||||
|
||||
# Manage membermanager users and groups
|
||||
if has_add_membermanager:
|
||||
# Add membermanager users and groups
|
||||
if membermanager_user_add or membermanager_group_add:
|
||||
commands.append([
|
||||
name, "hostgroup_add_member_manager",
|
||||
{
|
||||
"user": membermanager_user_add,
|
||||
"group": membermanager_group_add,
|
||||
}
|
||||
])
|
||||
# Remove membermanager users and groups
|
||||
if membermanager_user_del or membermanager_group_del:
|
||||
commands.append([
|
||||
name, "hostgroup_remove_member_manager",
|
||||
{
|
||||
"user": membermanager_user_del,
|
||||
"group": membermanager_group_del,
|
||||
}
|
||||
])
|
||||
|
||||
# Execute commands
|
||||
|
||||
changed = ansible_module.execute_ipa_commands(
|
||||
|
||||
@@ -243,7 +243,7 @@ def main():
|
||||
# present
|
||||
description = ansible_module.params_get("description")
|
||||
name = ansible_module.params_get("name")
|
||||
gid = ansible_module.params_get("gid")
|
||||
gid = ansible_module.params_get_with_type_cast("gid", int)
|
||||
|
||||
# runtime flags
|
||||
fallback_to_ldap = ansible_module.params_get("fallback_to_ldap")
|
||||
@@ -271,19 +271,6 @@ def main():
|
||||
|
||||
ansible_module.params_fail_used_invalid(invalid, state)
|
||||
|
||||
# Ensure parameter values are valid and have proper type.
|
||||
def int_or_empty_param(value, param):
|
||||
if value is not None and value != "":
|
||||
try:
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
ansible_module.fail_json(
|
||||
msg="Invalid value '%s' for argument '%s'" % (value, param)
|
||||
)
|
||||
return value
|
||||
|
||||
gid = int_or_empty_param(gid, "gid")
|
||||
|
||||
# Init
|
||||
|
||||
changed = False
|
||||
|
||||
@@ -87,7 +87,7 @@ options:
|
||||
sshpubkey:
|
||||
description: List of SSH public keys
|
||||
type: list
|
||||
element: str
|
||||
elements: str
|
||||
required: False
|
||||
aliases: ["ipasshpubkey"]
|
||||
certificate:
|
||||
@@ -113,7 +113,7 @@ options:
|
||||
description: |
|
||||
Suppress processing of membership attributes.
|
||||
Valid only if `state` is `absent`.
|
||||
type: str
|
||||
type: bool
|
||||
required: False
|
||||
aliases: ["no_members"]
|
||||
action:
|
||||
@@ -315,7 +315,7 @@ RETURN = """
|
||||
|
||||
from ansible.module_utils.ansible_freeipa_module import \
|
||||
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, gen_add_list, \
|
||||
gen_intersection_list, encode_certificate
|
||||
gen_intersection_list, encode_certificate, convert_input_certificates
|
||||
from ansible.module_utils import six
|
||||
|
||||
if six.PY3:
|
||||
@@ -439,9 +439,9 @@ def main():
|
||||
# present
|
||||
description = ansible_module.params_get("description")
|
||||
name = ansible_module.params_get("name")
|
||||
uid = ansible_module.params_get("uid")
|
||||
uid = ansible_module.params_get_with_type_cast("uid", int)
|
||||
gecos = ansible_module.params_get("gecos")
|
||||
gidnumber = ansible_module.params_get("gidnumber")
|
||||
gidnumber = ansible_module.params_get_with_type_cast("gidnumber", int)
|
||||
homedir = ansible_module.params_get("homedir")
|
||||
shell = ansible_module.params_get("shell")
|
||||
sshpubkey = ansible_module.params_get("sshpubkey")
|
||||
@@ -479,22 +479,8 @@ def main():
|
||||
|
||||
ansible_module.params_fail_used_invalid(invalid, state, action)
|
||||
|
||||
# Ensure parameter values are valid and have proper type.
|
||||
def int_or_empty_param(value, param):
|
||||
if value is not None and value != "":
|
||||
try:
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
ansible_module.fail_json(
|
||||
msg="Invalid value '%s' for argument '%s'" % (value, param)
|
||||
)
|
||||
return value
|
||||
|
||||
uid = int_or_empty_param(uid, "uid")
|
||||
gidnumber = int_or_empty_param(gidnumber, "gidnumber")
|
||||
|
||||
if certificate is not None:
|
||||
certificate = [cert.strip() for cert in certificate]
|
||||
certificate = convert_input_certificates(ansible_module, certificate,
|
||||
state)
|
||||
|
||||
# Init
|
||||
|
||||
|
||||
@@ -82,7 +82,6 @@ options:
|
||||
description: OAuth 2.0 client secret
|
||||
required: false
|
||||
type: str
|
||||
no_log: true
|
||||
aliases: ["ipaidpclientsecret"]
|
||||
scope:
|
||||
description: OAuth 2.0 scope. Multiple scopes separated by space
|
||||
@@ -185,7 +184,7 @@ RETURN = """
|
||||
|
||||
|
||||
from ansible.module_utils.ansible_freeipa_module import \
|
||||
IPAAnsibleModule, compare_args_ipa, template_str
|
||||
IPAAnsibleModule, compare_args_ipa, template_str, urlparse
|
||||
from ansible.module_utils import six
|
||||
from copy import deepcopy
|
||||
import string
|
||||
@@ -274,7 +273,14 @@ def find_idp(module, name):
|
||||
# An exception is raised if idp name is not found.
|
||||
return None
|
||||
|
||||
return _result["result"]
|
||||
res = _result["result"]
|
||||
|
||||
# Decode binary string secret
|
||||
if "ipaidpclientsecret" in res and len(res["ipaidpclientsecret"]) > 0:
|
||||
res["ipaidpclientsecret"][0] = \
|
||||
res["ipaidpclientsecret"][0].decode("ascii")
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def gen_args(auth_uri, dev_auth_uri, token_uri, userinfo_uri, keys_uri,
|
||||
@@ -333,6 +339,16 @@ def convert_provider_to_endpoints(module, _args, provider):
|
||||
_args.update(points)
|
||||
|
||||
|
||||
def validate_uri(module, uri):
|
||||
try:
|
||||
parsed = urlparse(uri, 'https')
|
||||
except Exception:
|
||||
module.fail_json(msg="Invalid URI '%s': not an https scheme" % uri)
|
||||
|
||||
if not parsed.netloc:
|
||||
module.fail_json(msg="Invalid URI '%s': missing netloc" % uri)
|
||||
|
||||
|
||||
def main():
|
||||
ansible_module = IPAAnsibleModule(
|
||||
argument_spec=dict(
|
||||
@@ -345,11 +361,11 @@ def main():
|
||||
dev_auth_uri=dict(required=False, type="str", default=None,
|
||||
aliases=["ipaidpdevauthendpoint"]),
|
||||
token_uri=dict(required=False, type="str", default=None,
|
||||
aliases=["ipaidptokenendpoint"]),
|
||||
aliases=["ipaidptokenendpoint"], no_log=False),
|
||||
userinfo_uri=dict(required=False, type="str", default=None,
|
||||
aliases=["ipaidpuserinfoendpoint"]),
|
||||
keys_uri=dict(required=False, type="str", default=None,
|
||||
aliases=["ipaidpkeysendpoint"]),
|
||||
aliases=["ipaidpkeysendpoint"], no_log=False),
|
||||
issuer_url=dict(required=False, type="str", default=None,
|
||||
aliases=["ipaidpissuerurl"]),
|
||||
client_id=dict(required=False, type="str", default=None,
|
||||
@@ -426,19 +442,6 @@ def main():
|
||||
if provider not in idp_providers:
|
||||
ansible_module.fail_json(
|
||||
msg="Provider '%s' is unknown" % provider)
|
||||
else:
|
||||
if not auth_uri:
|
||||
ansible_module.fail_json(
|
||||
msg="Parameter '%s' is missing" % "auth_uri")
|
||||
if not dev_auth_uri:
|
||||
ansible_module.fail_json(
|
||||
msg="Parameter '%s' is missing" % "dev_auth_uri")
|
||||
if not token_uri:
|
||||
ansible_module.fail_json(
|
||||
msg="Parameter '%s' is missing" % "token_uri")
|
||||
if not userinfo_uri:
|
||||
ansible_module.fail_json(
|
||||
msg="Parameter '%s' is missing" % "userinfo_uri")
|
||||
invalid = ["rename", "delete_continue"]
|
||||
else:
|
||||
# state renamed and absent
|
||||
@@ -459,6 +462,19 @@ def main():
|
||||
|
||||
ansible_module.params_fail_used_invalid(invalid, state)
|
||||
|
||||
# Empty client_id test
|
||||
if client_id is not None and client_id == "":
|
||||
ansible_module.fail_json(msg="'client_id' is required")
|
||||
|
||||
# Normalize base_url
|
||||
if base_url is not None and base_url.startswith('https://'):
|
||||
base_url = base_url[len('https://'):]
|
||||
|
||||
# Validate uris
|
||||
for uri in [auth_uri, dev_auth_uri, token_uri, userinfo_uri, keys_uri]:
|
||||
if uri is not None and uri != "":
|
||||
validate_uri(ansible_module, uri)
|
||||
|
||||
# Init
|
||||
|
||||
changed = False
|
||||
@@ -507,6 +523,19 @@ def main():
|
||||
res_find):
|
||||
commands.append([name, "idp_mod", args])
|
||||
else:
|
||||
if "ipaidpauthendpoint" not in args:
|
||||
ansible_module.fail_json(
|
||||
msg="Parameter '%s' is missing" % "auth_uri")
|
||||
if "ipaidpdevauthendpoint" not in args:
|
||||
ansible_module.fail_json(
|
||||
msg="Parameter '%s' is missing" % "dev_auth_uri")
|
||||
if "ipaidptokenendpoint" not in args:
|
||||
ansible_module.fail_json(
|
||||
msg="Parameter '%s' is missing" % "token_uri")
|
||||
if "ipaidpuserinfoendpoint" not in args:
|
||||
ansible_module.fail_json(
|
||||
msg="Parameter '%s' is missing" % "userinfo_uri")
|
||||
|
||||
commands.append([name, "idp_add", args])
|
||||
|
||||
elif state == "absent":
|
||||
|
||||
@@ -154,7 +154,7 @@ RETURN = """
|
||||
|
||||
|
||||
from ansible.module_utils.ansible_freeipa_module import \
|
||||
IPAAnsibleModule, compare_args_ipa
|
||||
IPAAnsibleModule, compare_args_ipa, to_text
|
||||
|
||||
|
||||
def find_permission(module, name):
|
||||
@@ -164,7 +164,12 @@ def find_permission(module, name):
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# An exception is raised if permission name is not found.
|
||||
return None
|
||||
return _result["result"]
|
||||
_res = _result["result"]
|
||||
for param in ["ipapermlocation", "ipapermtarget", "ipapermtargetto",
|
||||
"ipapermtargetfrom"]:
|
||||
if param in _res:
|
||||
_res[param] = [to_text(elem) for elem in _res[param]]
|
||||
return _res
|
||||
|
||||
|
||||
def gen_args(right, attrs, bindtype, subtree,
|
||||
|
||||
@@ -153,7 +153,7 @@ RETURN = """
|
||||
"""
|
||||
|
||||
from ansible.module_utils.ansible_freeipa_module import \
|
||||
IPAAnsibleModule, compare_args_ipa, boolean
|
||||
IPAAnsibleModule, compare_args_ipa
|
||||
|
||||
|
||||
def find_pwpolicy(module, name):
|
||||
@@ -294,20 +294,34 @@ def main():
|
||||
names = ansible_module.params_get("name")
|
||||
|
||||
# present
|
||||
maxlife = ansible_module.params_get("maxlife")
|
||||
minlife = ansible_module.params_get("minlife")
|
||||
history = ansible_module.params_get("history")
|
||||
minclasses = ansible_module.params_get("minclasses")
|
||||
minlength = ansible_module.params_get("minlength")
|
||||
priority = ansible_module.params_get("priority")
|
||||
maxfail = ansible_module.params_get("maxfail")
|
||||
failinterval = ansible_module.params_get("failinterval")
|
||||
lockouttime = ansible_module.params_get("lockouttime")
|
||||
maxrepeat = ansible_module.params_get("maxrepeat")
|
||||
maxsequence = ansible_module.params_get("maxsequence")
|
||||
dictcheck = ansible_module.params_get("dictcheck")
|
||||
usercheck = ansible_module.params_get("usercheck")
|
||||
gracelimit = ansible_module.params_get("gracelimit")
|
||||
maxlife = ansible_module.params_get_with_type_cast(
|
||||
"maxlife", int, allow_empty=True)
|
||||
minlife = ansible_module.params_get_with_type_cast(
|
||||
"minlife", int, allow_empty=True)
|
||||
history = ansible_module.params_get_with_type_cast(
|
||||
"history", int, allow_empty=True)
|
||||
minclasses = ansible_module.params_get_with_type_cast(
|
||||
"minclasses", int, allow_empty=True)
|
||||
minlength = ansible_module.params_get_with_type_cast(
|
||||
"minlength", int, allow_empty=True)
|
||||
priority = ansible_module.params_get_with_type_cast(
|
||||
"priority", int, allow_empty=True)
|
||||
maxfail = ansible_module.params_get_with_type_cast(
|
||||
"maxfail", int, allow_empty=True)
|
||||
failinterval = ansible_module.params_get_with_type_cast(
|
||||
"failinterval", int, allow_empty=True)
|
||||
lockouttime = ansible_module.params_get_with_type_cast(
|
||||
"lockouttime", int, allow_empty=True)
|
||||
maxrepeat = ansible_module.params_get_with_type_cast(
|
||||
"maxrepeat", int, allow_empty=True)
|
||||
maxsequence = ansible_module.params_get_with_type_cast(
|
||||
"maxsequence", int, allow_empty=True)
|
||||
dictcheck = ansible_module.params_get_with_type_cast(
|
||||
"dictcheck", bool, allow_empty=True)
|
||||
usercheck = ansible_module.params_get_with_type_cast(
|
||||
"usercheck", bool, allow_empty=True)
|
||||
gracelimit = ansible_module.params_get_with_type_cast(
|
||||
"gracelimit", int, allow_empty=True)
|
||||
|
||||
# state
|
||||
state = ansible_module.params_get("state")
|
||||
@@ -336,41 +350,6 @@ def main():
|
||||
|
||||
ansible_module.params_fail_used_invalid(invalid, state)
|
||||
|
||||
# Ensure parameter values are valid and have proper type.
|
||||
def int_or_empty_param(value, param):
|
||||
if value is not None and value != "":
|
||||
try:
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
ansible_module.fail_json(
|
||||
msg="Invalid value '%s' for argument '%s'" % (value, param)
|
||||
)
|
||||
return value
|
||||
|
||||
maxlife = int_or_empty_param(maxlife, "maxlife")
|
||||
minlife = int_or_empty_param(minlife, "minlife")
|
||||
history = int_or_empty_param(history, "history")
|
||||
minclasses = int_or_empty_param(minclasses, "minclasses")
|
||||
minlength = int_or_empty_param(minlength, "minlength")
|
||||
priority = int_or_empty_param(priority, "priority")
|
||||
maxfail = int_or_empty_param(maxfail, "maxfail")
|
||||
failinterval = int_or_empty_param(failinterval, "failinterval")
|
||||
lockouttime = int_or_empty_param(lockouttime, "lockouttime")
|
||||
maxrepeat = int_or_empty_param(maxrepeat, "maxrepeat")
|
||||
maxsequence = int_or_empty_param(maxsequence, "maxsequence")
|
||||
gracelimit = int_or_empty_param(gracelimit, "gracelimit")
|
||||
|
||||
def bool_or_empty_param(value, param): # pylint: disable=R1710
|
||||
if value is None or value == "":
|
||||
return value
|
||||
try:
|
||||
return boolean(value)
|
||||
except TypeError as terr:
|
||||
ansible_module.fail_json(msg="Param '%s': %s" % (param, str(terr)))
|
||||
|
||||
dictcheck = bool_or_empty_param(dictcheck, "dictcheck")
|
||||
usercheck = bool_or_empty_param(usercheck, "usercheck")
|
||||
|
||||
# Ensure gracelimit has proper limit.
|
||||
if gracelimit:
|
||||
if gracelimit < -1:
|
||||
|
||||
@@ -293,7 +293,7 @@ def result_get_value_lowercase(res_find, key, default=None):
|
||||
if existing is not None:
|
||||
if isinstance(existing, (list, tuple)):
|
||||
existing = [to_text(item).lower() for item in existing]
|
||||
if isinstance(existing, (str, unicode)):
|
||||
if isinstance(existing, (str, unicode)): # pylint: disable=W0012,E0606
|
||||
existing = existing.lower()
|
||||
else:
|
||||
existing = default
|
||||
|
||||
@@ -44,7 +44,7 @@ options:
|
||||
description: The service to manage
|
||||
type: list
|
||||
elements: str
|
||||
required: true
|
||||
required: false
|
||||
aliases: ["service"]
|
||||
services:
|
||||
description: The list of service dicts.
|
||||
@@ -167,6 +167,13 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["ipaallowedtoperform_read_keys_hostgroup"]
|
||||
delete_continue:
|
||||
description:
|
||||
Continuous mode. Don't stop on errors.
|
||||
Valid only if `state` is `absent`.
|
||||
required: false
|
||||
type: bool
|
||||
aliases: ["continue"]
|
||||
certificate:
|
||||
description: Base-64 encoded service certificate.
|
||||
required: false
|
||||
@@ -370,6 +377,43 @@ EXAMPLES = """
|
||||
host:
|
||||
- host1.example.com
|
||||
- name: HTTP/www.service.com
|
||||
|
||||
# Ensure multiple services are present
|
||||
- ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services:
|
||||
- name: HTTP/www.example.com
|
||||
principal:
|
||||
- host/host1.example.com
|
||||
- name: mysvc/www.example.com
|
||||
pac_type: NONE
|
||||
ok_as_delegate: yes
|
||||
ok_to_auth_as_delegate: yes
|
||||
- name: HTTP/www.example.com
|
||||
allow_create_keytab_user:
|
||||
- user01
|
||||
- user02
|
||||
allow_create_keytab_group:
|
||||
- group01
|
||||
- group02
|
||||
allow_create_keytab_host:
|
||||
- host1.example.com
|
||||
- host2.example.com
|
||||
allow_create_keytab_hostgroup:
|
||||
- hostgroup01
|
||||
- hostgroup02
|
||||
- name: mysvc/host2.example.com
|
||||
auth_ind: otp,radius
|
||||
|
||||
# Ensure service host members are present
|
||||
- ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services:
|
||||
- name: HTTP/www1.example.com
|
||||
host: host1.example.com
|
||||
- name: HTTP/www2.example.com
|
||||
host: host2.example.com
|
||||
action: member
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
@@ -378,7 +422,7 @@ RETURN = """
|
||||
from ansible.module_utils.ansible_freeipa_module import \
|
||||
IPAAnsibleModule, compare_args_ipa, encode_certificate, \
|
||||
gen_add_del_lists, gen_add_list, gen_intersection_list, ipalib_errors, \
|
||||
api_get_realm, to_text
|
||||
api_get_realm, to_text, convert_input_certificates
|
||||
from ansible.module_utils import six
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
@@ -601,14 +645,10 @@ def main():
|
||||
# service attributes
|
||||
principal = ansible_module.params_get("principal")
|
||||
certificate = ansible_module.params_get("certificate")
|
||||
# Any leading or trailing whitespace is removed while adding the
|
||||
# certificate with serive_add_cert. To be able to compare the results
|
||||
# from service_show with the given certificates we have to remove the
|
||||
# white space also.
|
||||
if certificate is not None:
|
||||
certificate = [cert.strip() for cert in certificate]
|
||||
pac_type = ansible_module.params_get("pac_type", allow_empty_string=True)
|
||||
auth_ind = ansible_module.params_get("auth_ind", allow_empty_string=True)
|
||||
pac_type = ansible_module.params_get(
|
||||
"pac_type", allow_empty_list_item=True)
|
||||
auth_ind = ansible_module.params_get(
|
||||
"auth_ind", allow_empty_list_item=True)
|
||||
skip_host_check = ansible_module.params_get("skip_host_check")
|
||||
force = ansible_module.params_get("force")
|
||||
requires_pre_auth = ansible_module.params_get("requires_pre_auth")
|
||||
@@ -634,6 +674,8 @@ def main():
|
||||
ansible_module.fail_json(msg="At least one name or services is "
|
||||
"required")
|
||||
check_parameters(ansible_module, state, action, names)
|
||||
certificate = convert_input_certificates(ansible_module, certificate,
|
||||
state)
|
||||
|
||||
# Use services if names is None
|
||||
if services is not None:
|
||||
@@ -667,12 +709,8 @@ def main():
|
||||
service_set.add(name)
|
||||
principal = service.get("principal")
|
||||
certificate = service.get("certificate")
|
||||
# Any leading or trailing whitespace is removed while adding
|
||||
# the certificate with serive_add_cert. To be able to compare
|
||||
# the results from service_show with the given certificates
|
||||
# we have to remove the white space also.
|
||||
if certificate is not None:
|
||||
certificate = [cert.strip() for cert in certificate]
|
||||
certificate = convert_input_certificates(ansible_module,
|
||||
certificate, state)
|
||||
pac_type = service.get("pac_type")
|
||||
auth_ind = service.get("auth_ind")
|
||||
check_authind(ansible_module, auth_ind)
|
||||
@@ -691,7 +729,11 @@ def main():
|
||||
|
||||
delete_continue = service.get("delete_continue")
|
||||
|
||||
elif isinstance(service, (str, unicode)):
|
||||
elif (
|
||||
isinstance(
|
||||
service, (str, unicode) # pylint: disable=W0012,E0606
|
||||
)
|
||||
):
|
||||
name = service
|
||||
else:
|
||||
ansible_module.fail_json(msg="Service '%s' is not valid" %
|
||||
@@ -838,7 +880,9 @@ def main():
|
||||
elif state == "absent":
|
||||
if action == "service":
|
||||
if res_find is not None:
|
||||
args = {'continue': delete_continue}
|
||||
args = {}
|
||||
if delete_continue is not None:
|
||||
args['continue'] = delete_continue
|
||||
commands.append([name, 'service_del', args])
|
||||
|
||||
elif action == "member":
|
||||
@@ -927,7 +971,7 @@ def main():
|
||||
|
||||
# Execute commands
|
||||
changed = ansible_module.execute_ipa_commands(
|
||||
commands, fail_on_member_errors=True)
|
||||
commands, batch=True, keeponly=[], fail_on_member_errors=True)
|
||||
|
||||
# Done
|
||||
ansible_module.exit_json(changed=changed, **exit_args)
|
||||
|
||||
@@ -138,6 +138,11 @@ options:
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
runasuser_group:
|
||||
description: List of groups for Sudo to execute as.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
runasgroup:
|
||||
description: List of groups for Sudo to execute as.
|
||||
required: false
|
||||
@@ -214,6 +219,12 @@ EXAMPLES = """
|
||||
hostmask:
|
||||
- 192.168.122.1/24
|
||||
- 192.168.120.1/24
|
||||
|
||||
# Ensure sudorule 'runasuser' has 'ipasuers' group as runas users.
|
||||
- ipasudorule:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testrule1
|
||||
runasuser_group: ipausers
|
||||
action: member
|
||||
|
||||
# Ensure Sudo Rule tesrule1 is absent
|
||||
@@ -315,6 +326,8 @@ def main():
|
||||
default=None),
|
||||
runasgroup=dict(required=False, type="list", elements="str",
|
||||
default=None),
|
||||
runasuser_group=dict(required=False, type="list", elements="str",
|
||||
default=None),
|
||||
order=dict(type="int", required=False, aliases=['sudoorder']),
|
||||
sudooption=dict(required=False, type='list', elements="str",
|
||||
default=None, aliases=["options"]),
|
||||
@@ -362,6 +375,7 @@ def main():
|
||||
sudooption = ansible_module.params_get("sudooption")
|
||||
order = ansible_module.params_get("order")
|
||||
runasuser = ansible_module.params_get_lowercase("runasuser")
|
||||
runasuser_group = ansible_module.params_get_lowercase("runasuser_group")
|
||||
runasgroup = ansible_module.params_get_lowercase("runasgroup")
|
||||
action = ansible_module.params_get("action")
|
||||
|
||||
@@ -406,7 +420,8 @@ def main():
|
||||
invalid.extend(["host", "hostgroup", "hostmask", "user", "group",
|
||||
"runasuser", "runasgroup", "allow_sudocmd",
|
||||
"allow_sudocmdgroup", "deny_sudocmd",
|
||||
"deny_sudocmdgroup", "sudooption"])
|
||||
"deny_sudocmdgroup", "sudooption",
|
||||
"runasuser_group"])
|
||||
|
||||
elif state in ["enabled", "disabled"]:
|
||||
if len(names) < 1:
|
||||
@@ -420,7 +435,7 @@ def main():
|
||||
"nomembers", "nomembers", "host", "hostgroup", "hostmask",
|
||||
"user", "group", "allow_sudocmd", "allow_sudocmdgroup",
|
||||
"deny_sudocmd", "deny_sudocmdgroup", "runasuser",
|
||||
"runasgroup", "order", "sudooption"]
|
||||
"runasgroup", "order", "sudooption", "runasuser_group"]
|
||||
else:
|
||||
ansible_module.fail_json(msg="Invalid state '%s'" % state)
|
||||
|
||||
@@ -453,6 +468,7 @@ def main():
|
||||
deny_cmdgroup_add, deny_cmdgroup_del = [], []
|
||||
sudooption_add, sudooption_del = [], []
|
||||
runasuser_add, runasuser_del = [], []
|
||||
runasuser_group_add, runasuser_group_del = [], []
|
||||
runasgroup_add, runasgroup_del = [], []
|
||||
|
||||
for name in names:
|
||||
@@ -552,6 +568,12 @@ def main():
|
||||
+ res_find.get('ipasudorunasextuser', [])
|
||||
)
|
||||
)
|
||||
runasuser_group_add, runasuser_group_del = (
|
||||
gen_add_del_lists(
|
||||
runasuser_group,
|
||||
res_find.get('ipasudorunas_group', [])
|
||||
)
|
||||
)
|
||||
|
||||
# runasgroup attribute can be used with both IPA and
|
||||
# non-IPA (external) groups. IPA will handle the correct
|
||||
@@ -623,6 +645,11 @@ def main():
|
||||
(list(res_find.get('ipasudorunas_user', []))
|
||||
+ list(res_find.get('ipasudorunasextuser', [])))
|
||||
)
|
||||
if runasuser_group is not None:
|
||||
runasuser_group_add = gen_add_list(
|
||||
runasuser_group,
|
||||
res_find.get('ipasudorunas_group', [])
|
||||
)
|
||||
# runasgroup attribute can be used with both IPA and
|
||||
# non-IPA (external) groups, so we need to compare
|
||||
# the provided list against both users and external
|
||||
@@ -703,6 +730,11 @@ def main():
|
||||
+ list(res_find.get('ipasudorunasextuser', []))
|
||||
)
|
||||
)
|
||||
if runasuser_group is not None:
|
||||
runasuser_group_del = gen_intersection_list(
|
||||
runasuser_group,
|
||||
res_find.get('ipasudorunas_group', [])
|
||||
)
|
||||
# runasgroup attribute can be used with both IPA and
|
||||
# non-IPA (external) groups, so we need to compare
|
||||
# the provided list against both groups and external
|
||||
@@ -812,13 +844,19 @@ def main():
|
||||
}
|
||||
])
|
||||
# Manage RunAS users
|
||||
if runasuser_add:
|
||||
if runasuser_add or runasuser_group_add:
|
||||
# Can't use empty lists with command "sudorule_add_runasuser".
|
||||
_args = {}
|
||||
if runasuser_add:
|
||||
_args["user"] = runasuser_add
|
||||
if runasuser_group_add:
|
||||
_args["group"] = runasuser_group_add
|
||||
commands.append([name, "sudorule_add_runasuser", _args])
|
||||
if runasuser_del or runasuser_group_del:
|
||||
commands.append([
|
||||
name, "sudorule_add_runasuser", {"user": runasuser_add}
|
||||
])
|
||||
if runasuser_del:
|
||||
commands.append([
|
||||
name, "sudorule_remove_runasuser", {"user": runasuser_del}
|
||||
name,
|
||||
"sudorule_remove_runasuser",
|
||||
{"user": runasuser_del, "group": runasuser_group_del}
|
||||
])
|
||||
|
||||
# Manage RunAS Groups
|
||||
|
||||
@@ -319,6 +319,11 @@ options:
|
||||
description: Suppress processing of membership attributes
|
||||
required: false
|
||||
type: bool
|
||||
rename:
|
||||
description: Rename the user object
|
||||
required: false
|
||||
type: str
|
||||
aliases: ["new_name"]
|
||||
required: false
|
||||
first:
|
||||
description: The first name. Required if user does not exist.
|
||||
@@ -586,6 +591,11 @@ options:
|
||||
description: Suppress processing of membership attributes
|
||||
required: false
|
||||
type: bool
|
||||
rename:
|
||||
description: Rename the user object
|
||||
required: false
|
||||
type: str
|
||||
aliases: ["new_name"]
|
||||
preserve:
|
||||
description: Delete a user, keeping the entry available for future use
|
||||
required: false
|
||||
@@ -607,7 +617,8 @@ options:
|
||||
default: present
|
||||
choices: ["present", "absent",
|
||||
"enabled", "disabled",
|
||||
"unlocked", "undeleted"]
|
||||
"unlocked", "undeleted",
|
||||
"renamed"]
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
"""
|
||||
@@ -694,6 +705,13 @@ EXAMPLES = """
|
||||
smb_profile_path: \\\\server\\profiles\\some_profile
|
||||
smb_home_dir: \\\\users\\home\\smbuser
|
||||
smb_home_drive: "U:"
|
||||
|
||||
# Rename an existing user
|
||||
- ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: someuser
|
||||
rename: anotheruser
|
||||
state: renamed
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
@@ -723,7 +741,8 @@ user:
|
||||
from ansible.module_utils.ansible_freeipa_module import \
|
||||
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, date_format, \
|
||||
encode_certificate, load_cert_from_str, DN_x500_text, to_text, \
|
||||
ipalib_errors
|
||||
ipalib_errors, gen_add_list, gen_intersection_list, \
|
||||
convert_input_certificates
|
||||
from ansible.module_utils import six
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
@@ -857,7 +876,7 @@ def check_parameters( # pylint: disable=unused-argument
|
||||
employeenumber, employeetype, preferredlanguage, certificate,
|
||||
certmapdata, noprivate, nomembers, preserve, update_password,
|
||||
smb_logon_script, smb_profile_path, smb_home_dir, smb_home_drive,
|
||||
idp, ipa_user_id,
|
||||
idp, ipa_user_id, rename
|
||||
):
|
||||
if state == "present" and action == "user":
|
||||
invalid = ["preserve"]
|
||||
@@ -885,6 +904,19 @@ def check_parameters( # pylint: disable=unused-argument
|
||||
module.fail_json(
|
||||
msg="Preserve is only possible for state=absent")
|
||||
|
||||
if state != "renamed":
|
||||
invalid.append("rename")
|
||||
else:
|
||||
invalid.extend([
|
||||
"preserve", "principal", "manager", "certificate", "certmapdata",
|
||||
])
|
||||
if not rename:
|
||||
module.fail_json(
|
||||
msg="A value for attribute 'rename' must be provided.")
|
||||
if action == "member":
|
||||
module.fail_json(
|
||||
msg="Action member can not be used with state: renamed.")
|
||||
|
||||
module.params_fail_used_invalid(invalid, state, action)
|
||||
|
||||
if certmapdata is not None:
|
||||
@@ -975,9 +1007,8 @@ def gen_certmapdata_args(certmapdata):
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def result_handler(module, result, command, name, args, errors, exit_args,
|
||||
single_user):
|
||||
|
||||
def result_handler(module, result, command, name, args, exit_args,
|
||||
errors, single_user):
|
||||
if "random" in args and command in ["user_add", "user_mod"] \
|
||||
and "randompassword" in result["result"]:
|
||||
if single_user:
|
||||
@@ -987,31 +1018,8 @@ def result_handler(module, result, command, name, args, errors, exit_args,
|
||||
exit_args.setdefault(name, {})["randompassword"] = \
|
||||
result["result"]["randompassword"]
|
||||
|
||||
# Get all errors
|
||||
# All "already a member" and "not a member" failures in the
|
||||
# result are ignored. All others are reported.
|
||||
if "failed" in result and len(result["failed"]) > 0:
|
||||
for item in result["failed"]:
|
||||
failed_item = result["failed"][item]
|
||||
for member_type in failed_item:
|
||||
for member, failure in failed_item[member_type]:
|
||||
if "already a member" in failure \
|
||||
or "not a member" in failure:
|
||||
continue
|
||||
errors.append("%s: %s %s: %s" % (
|
||||
command, member_type, member, failure))
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def exception_handler(module, ex, errors, exit_args, single_user):
|
||||
msg = str(ex)
|
||||
if "already contains" in msg \
|
||||
or "does not contain" in msg:
|
||||
return True
|
||||
# The canonical principal name may not be removed
|
||||
if "equal to the canonical principal name must" in msg:
|
||||
return True
|
||||
return False
|
||||
IPAAnsibleModule.member_error_handler(module, result, command, name, args,
|
||||
errors)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -1096,7 +1104,9 @@ def main():
|
||||
nomembers=dict(type='bool', default=None),
|
||||
idp=dict(type="str", default=None, aliases=['ipaidpconfiglink']),
|
||||
idp_user_id=dict(type="str", default=None,
|
||||
aliases=['ipaidpconfiglink']),
|
||||
aliases=['ipaidpsub']),
|
||||
rename=dict(type="str", required=False, default=None,
|
||||
aliases=["new_name"]),
|
||||
)
|
||||
|
||||
ansible_module = IPAAnsibleModule(
|
||||
@@ -1128,7 +1138,7 @@ def main():
|
||||
choices=["member", "user"]),
|
||||
state=dict(type="str", default="present",
|
||||
choices=["present", "absent", "enabled", "disabled",
|
||||
"unlocked", "undeleted"]),
|
||||
"unlocked", "undeleted", "renamed"]),
|
||||
|
||||
# Add user specific parameters for simple use case
|
||||
**user_spec
|
||||
@@ -1185,9 +1195,9 @@ def main():
|
||||
manager = ansible_module.params_get("manager")
|
||||
carlicense = ansible_module.params_get("carlicense")
|
||||
sshpubkey = ansible_module.params_get("sshpubkey",
|
||||
allow_empty_string=True)
|
||||
allow_empty_list_item=True)
|
||||
userauthtype = ansible_module.params_get("userauthtype",
|
||||
allow_empty_string=True)
|
||||
allow_empty_list_item=True)
|
||||
userclass = ansible_module.params_get("userclass")
|
||||
radius = ansible_module.params_get("radius")
|
||||
radiususer = ansible_module.params_get("radiususer")
|
||||
@@ -1209,6 +1219,8 @@ def main():
|
||||
preserve = ansible_module.params_get("preserve")
|
||||
# mod
|
||||
update_password = ansible_module.params_get("update_password")
|
||||
# rename
|
||||
rename = ansible_module.params_get("rename")
|
||||
# general
|
||||
action = ansible_module.params_get("action")
|
||||
state = ansible_module.params_get("state")
|
||||
@@ -1219,27 +1231,32 @@ def main():
|
||||
(users is None or len(users) < 1):
|
||||
ansible_module.fail_json(msg="One of name and users is required")
|
||||
|
||||
if state == "present":
|
||||
if state in ["present", "renamed"]:
|
||||
if names is not None and len(names) != 1:
|
||||
act = "renamed" if state == "renamed" else "added"
|
||||
ansible_module.fail_json(
|
||||
msg="Only one user can be added at a time using name.")
|
||||
|
||||
check_parameters(
|
||||
ansible_module, state, action,
|
||||
first, last, fullname, displayname, initials, homedir, gecos, shell,
|
||||
email,
|
||||
principal, principalexpiration, passwordexpiration, password, random,
|
||||
uid, gid, street, city, phone, mobile, pager, fax, orgunit, title,
|
||||
manager, carlicense, sshpubkey, userauthtype, userclass, radius,
|
||||
radiususer, departmentnumber, employeenumber, employeetype,
|
||||
preferredlanguage, certificate, certmapdata, noprivate, nomembers,
|
||||
preserve, update_password, smb_logon_script, smb_profile_path,
|
||||
smb_home_dir, smb_home_drive, idp, idp_user_id)
|
||||
certmapdata = convert_certmapdata(certmapdata)
|
||||
msg="Only one user can be %s at a time using name." % (act))
|
||||
|
||||
# Use users if names is None
|
||||
if users is not None:
|
||||
names = users
|
||||
else:
|
||||
check_parameters(
|
||||
ansible_module, state, action,
|
||||
first, last, fullname, displayname, initials, homedir, gecos,
|
||||
shell, email,
|
||||
principal, principalexpiration, passwordexpiration, password,
|
||||
random,
|
||||
uid, gid, street, city, phone, mobile, pager, fax, orgunit, title,
|
||||
manager, carlicense, sshpubkey, userauthtype, userclass, radius,
|
||||
radiususer, departmentnumber, employeenumber, employeetype,
|
||||
preferredlanguage, certificate, certmapdata, noprivate, nomembers,
|
||||
preserve, update_password, smb_logon_script, smb_profile_path,
|
||||
smb_home_dir, smb_home_drive, idp, idp_user_id, rename,
|
||||
)
|
||||
certificate = convert_input_certificates(ansible_module, certificate,
|
||||
state)
|
||||
certmapdata = convert_certmapdata(certmapdata)
|
||||
|
||||
# Init
|
||||
|
||||
@@ -1330,6 +1347,7 @@ def main():
|
||||
smb_home_drive = user.get("smb_home_drive")
|
||||
idp = user.get("idp")
|
||||
idp_user_id = user.get("idp_user_id")
|
||||
rename = user.get("rename")
|
||||
certificate = user.get("certificate")
|
||||
certmapdata = user.get("certmapdata")
|
||||
noprivate = user.get("noprivate")
|
||||
@@ -1346,7 +1364,10 @@ def main():
|
||||
employeetype, preferredlanguage, certificate,
|
||||
certmapdata, noprivate, nomembers, preserve,
|
||||
update_password, smb_logon_script, smb_profile_path,
|
||||
smb_home_dir, smb_home_drive, idp, idp_user_id)
|
||||
smb_home_dir, smb_home_drive, idp, idp_user_id, rename,
|
||||
)
|
||||
certificate = convert_input_certificates(ansible_module,
|
||||
certificate, state)
|
||||
certmapdata = convert_certmapdata(certmapdata)
|
||||
|
||||
# Check API specific parameters
|
||||
@@ -1357,7 +1378,11 @@ def main():
|
||||
|
||||
email = extend_emails(email, default_email_domain)
|
||||
|
||||
elif isinstance(user, (str, unicode)):
|
||||
elif (
|
||||
isinstance(
|
||||
user, (str, unicode) # pylint: disable=W0012,E0606
|
||||
)
|
||||
):
|
||||
name = user
|
||||
else:
|
||||
ansible_module.fail_json(msg="User '%s' is not valid" %
|
||||
@@ -1449,6 +1474,10 @@ def main():
|
||||
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"]
|
||||
if "noprivate" in args:
|
||||
del args["noprivate"]
|
||||
|
||||
@@ -1602,10 +1631,12 @@ def main():
|
||||
msg="No user '%s'" % name)
|
||||
|
||||
# Ensure managers are present
|
||||
if manager is not None and len(manager) > 0:
|
||||
manager_add = gen_add_list(
|
||||
manager, res_find.get("manager"))
|
||||
if manager_add is not None and len(manager_add) > 0:
|
||||
commands.append([name, "user_add_manager",
|
||||
{
|
||||
"user": manager,
|
||||
"user": manager_add,
|
||||
}])
|
||||
|
||||
# Principals need to be added and removed one by one,
|
||||
@@ -1614,8 +1645,10 @@ def main():
|
||||
# the removal of non-existing entries.
|
||||
|
||||
# Ensure principals are present
|
||||
if principal is not None and len(principal) > 0:
|
||||
for _principal in principal:
|
||||
principal_add = gen_add_list(
|
||||
principal, res_find.get("krbprincipalname"))
|
||||
if principal_add is not None and len(principal_add) > 0:
|
||||
for _principal in principal_add:
|
||||
commands.append([name, "user_add_principal",
|
||||
{
|
||||
"krbprincipalname":
|
||||
@@ -1628,8 +1661,11 @@ def main():
|
||||
# the removal of non-existing entries.
|
||||
|
||||
# Ensure certificates are present
|
||||
if certificate is not None and len(certificate) > 0:
|
||||
for _certificate in certificate:
|
||||
certificate_add = gen_add_list(
|
||||
certificate, res_find.get("usercertificate"))
|
||||
if certificate_add is not None and \
|
||||
len(certificate_add) > 0:
|
||||
for _certificate in certificate_add:
|
||||
commands.append([name, "user_add_cert",
|
||||
{
|
||||
"usercertificate":
|
||||
@@ -1641,8 +1677,11 @@ def main():
|
||||
# one reliably (https://pagure.io/freeipa/issue/8097)
|
||||
|
||||
# Ensure certmapdata are present
|
||||
if certmapdata is not None and len(certmapdata) > 0:
|
||||
for _data in certmapdata:
|
||||
certmapdata_add = gen_add_list(
|
||||
certmapdata, res_find.get("ipacertmapdata"))
|
||||
if certmapdata_add is not None and \
|
||||
len(certmapdata_add) > 0:
|
||||
for _data in certmapdata_add:
|
||||
commands.append([name, "user_add_certmapdata",
|
||||
gen_certmapdata_args(_data)])
|
||||
|
||||
@@ -1663,10 +1702,12 @@ def main():
|
||||
msg="No user '%s'" % name)
|
||||
|
||||
# Ensure managers are absent
|
||||
if manager is not None and len(manager) > 0:
|
||||
manager_del = gen_intersection_list(
|
||||
manager, res_find.get("manager"))
|
||||
if manager_del is not None and len(manager_del) > 0:
|
||||
commands.append([name, "user_remove_manager",
|
||||
{
|
||||
"user": manager,
|
||||
"user": manager_del,
|
||||
}])
|
||||
|
||||
# Principals need to be added and removed one by one,
|
||||
@@ -1675,10 +1716,12 @@ def main():
|
||||
# the removal of non-existing entries.
|
||||
|
||||
# Ensure principals are absent
|
||||
if principal is not None and len(principal) > 0:
|
||||
principal_del = gen_intersection_list(
|
||||
principal, res_find.get("krbprincipalname"))
|
||||
if principal_del is not None and len(principal_del) > 0:
|
||||
commands.append([name, "user_remove_principal",
|
||||
{
|
||||
"krbprincipalname": principal,
|
||||
"krbprincipalname": principal_del,
|
||||
}])
|
||||
|
||||
# Certificates need to be added and removed one by one,
|
||||
@@ -1687,8 +1730,11 @@ def main():
|
||||
# the removal of non-existing entries.
|
||||
|
||||
# Ensure certificates are absent
|
||||
if certificate is not None and len(certificate) > 0:
|
||||
for _certificate in certificate:
|
||||
certificate_del = gen_intersection_list(
|
||||
certificate, res_find.get("usercertificate"))
|
||||
if certificate_del is not None and \
|
||||
len(certificate_del) > 0:
|
||||
for _certificate in certificate_del:
|
||||
commands.append([name, "user_remove_cert",
|
||||
{
|
||||
"usercertificate":
|
||||
@@ -1700,10 +1746,13 @@ def main():
|
||||
# one reliably (https://pagure.io/freeipa/issue/8097)
|
||||
|
||||
# Ensure certmapdata are absent
|
||||
if certmapdata is not None and len(certmapdata) > 0:
|
||||
certmapdata_del = gen_intersection_list(
|
||||
certmapdata, res_find.get("ipacertmapdata"))
|
||||
if certmapdata_del is not None and \
|
||||
len(certmapdata_del) > 0:
|
||||
# Using issuer and subject can only be done one by
|
||||
# one reliably (https://pagure.io/freeipa/issue/8097)
|
||||
for _data in certmapdata:
|
||||
for _data in certmapdata_del:
|
||||
commands.append([name, "user_remove_certmapdata",
|
||||
gen_certmapdata_args(_data)])
|
||||
elif state == "undeleted":
|
||||
@@ -1733,6 +1782,12 @@ def main():
|
||||
else:
|
||||
raise ValueError("No user '%s'" % name)
|
||||
|
||||
elif state == "renamed":
|
||||
if res_find is None:
|
||||
ansible_module.fail_json(msg="No user '%s'" % name)
|
||||
else:
|
||||
if rename != name:
|
||||
commands.append([name, 'user_mod', {"rename": rename}])
|
||||
else:
|
||||
ansible_module.fail_json(msg="Unkown state '%s'" % state)
|
||||
|
||||
@@ -1741,7 +1796,7 @@ def main():
|
||||
# Execute commands
|
||||
|
||||
changed = ansible_module.execute_ipa_commands(
|
||||
commands, result_handler, exception_handler,
|
||||
commands, result_handler, batch=True, keeponly=["randompassword"],
|
||||
exit_args=exit_args, single_user=users is None)
|
||||
|
||||
# Done
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
-r requirements-tests.txt
|
||||
ipdb==0.13.4
|
||||
pre-commit==2.20.0
|
||||
flake8==6.0.0
|
||||
flake8==7.0.0
|
||||
flake8-bugbear
|
||||
pylint==2.17.2
|
||||
pylint>=3.2
|
||||
wrapt==1.14.1
|
||||
pydocstyle==6.3.0
|
||||
yamllint==1.32.0
|
||||
ansible-lint
|
||||
yamllint==1.35.1
|
||||
ansible-lint>=24.5.0
|
||||
|
||||
@@ -42,7 +42,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -6,7 +6,7 @@ galaxy_info:
|
||||
description: A role to backup and restore an IPA server
|
||||
company: Red Hat, Inc
|
||||
license: GPLv3
|
||||
min_ansible_version: "2.13"
|
||||
min_ansible_version: "2.15"
|
||||
platforms:
|
||||
- name: Fedora
|
||||
versions:
|
||||
|
||||
@@ -34,7 +34,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
@@ -201,6 +201,7 @@ Variable | Description | Required
|
||||
`ipasssd_preserve_sssd` | The bool value defines if the old SSSD configuration will be preserved if it is not possible to merge it with a new one. `ipasssd_preserve_sssd` defaults to `no`. | no
|
||||
`ipaclient_request_cert` | The bool value defines if the certificate for the machine wil be requested. The certificate will be stored in /etc/ipa/nssdb under the nickname "Local IPA host". . `ipaclient_request_cert` defaults to `no`. The option is deprecated and will be removed in a future release. | no
|
||||
`ipaclient_keytab` | The string value contains the path on the node of a backup host keytab from a previous enrollment. | no
|
||||
`ipaclient_automount_location` | Automount location | no
|
||||
|
||||
|
||||
Server Variables
|
||||
|
||||
@@ -152,8 +152,10 @@ def configure_dns_resolver(nameservers, searchdomains, fstore=None):
|
||||
if not searchdomains or not isinstance(searchdomains, list):
|
||||
raise AssertionError("searchdomains must be of type list")
|
||||
|
||||
changed = False
|
||||
if fstore is not None and not fstore.has_file(paths.RESOLV_CONF):
|
||||
fstore.backup_file(paths.RESOLV_CONF)
|
||||
changed = True
|
||||
|
||||
resolve1_enabled = detect_resolve1_resolv_conf()
|
||||
if "NetworkManager" not in services.knownservices:
|
||||
@@ -192,6 +194,7 @@ def configure_dns_resolver(nameservers, searchdomains, fstore=None):
|
||||
sdrd_service = services.service("systemd-resolved.service")
|
||||
if sdrd_service.is_enabled():
|
||||
sdrd_service.reload_or_restart()
|
||||
changed = True
|
||||
|
||||
# Then configure NetworkManager or resolve.conf
|
||||
if nm_service.is_enabled():
|
||||
@@ -217,6 +220,7 @@ def configure_dns_resolver(nameservers, searchdomains, fstore=None):
|
||||
outf.write(cfg)
|
||||
# reload NetworkManager
|
||||
nm_service.reload_or_restart()
|
||||
changed = True
|
||||
|
||||
# Configure resolv.conf if NetworkManager and systemd-resoled are not
|
||||
# enabled
|
||||
@@ -231,6 +235,9 @@ def configure_dns_resolver(nameservers, searchdomains, fstore=None):
|
||||
cfg.append("nameserver %s" % nameserver)
|
||||
with open(paths.RESOLV_CONF, 'w') as outf:
|
||||
outf.write('\n'.join(cfg))
|
||||
changed = True
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def unconfigure_dns_resolver(fstore=None):
|
||||
@@ -239,8 +246,11 @@ def unconfigure_dns_resolver(fstore=None):
|
||||
|
||||
:param fstore: optional file store for resolv.conf restore
|
||||
"""
|
||||
changed = False
|
||||
|
||||
if fstore is not None and fstore.has_file(paths.RESOLV_CONF):
|
||||
fstore.restore_file(paths.RESOLV_CONF)
|
||||
changed = True
|
||||
|
||||
if os.path.isfile(NETWORK_MANAGER_IPA_CONF):
|
||||
os.unlink(NETWORK_MANAGER_IPA_CONF)
|
||||
@@ -252,6 +262,7 @@ def unconfigure_dns_resolver(fstore=None):
|
||||
nm_service = services.knownservices['NetworkManager']
|
||||
if nm_service.is_enabled():
|
||||
nm_service.reload_or_restart()
|
||||
changed = True
|
||||
|
||||
if os.path.isfile(SYSTEMD_RESOLVED_IPA_CONF):
|
||||
os.unlink(SYSTEMD_RESOLVED_IPA_CONF)
|
||||
@@ -261,6 +272,9 @@ def unconfigure_dns_resolver(fstore=None):
|
||||
sdrd_service = services.service("systemd-resolved.service")
|
||||
if sdrd_service.is_enabled():
|
||||
sdrd_service.reload_or_restart()
|
||||
changed = True
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def main():
|
||||
@@ -308,11 +322,12 @@ def main():
|
||||
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
|
||||
|
||||
if state == "present":
|
||||
configure_dns_resolver(nameservers, searchdomains, fstore)
|
||||
changed = configure_dns_resolver(nameservers,
|
||||
searchdomains, fstore)
|
||||
else:
|
||||
unconfigure_dns_resolver(fstore)
|
||||
changed = unconfigure_dns_resolver(fstore)
|
||||
|
||||
module.exit_json(changed=True)
|
||||
module.exit_json(changed=changed)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -89,9 +89,13 @@ try:
|
||||
from ipapython.ipautil import run
|
||||
from ipalib.constants import DEFAULT_CONFIG
|
||||
try:
|
||||
from ipalib.install.kinit import kinit_password, kinit_keytab
|
||||
from ipalib.kinit import kinit_password, kinit_keytab
|
||||
except ImportError:
|
||||
from ipapython.ipautil import kinit_password, kinit_keytab
|
||||
try:
|
||||
from ipalib.install.kinit import kinit_password, kinit_keytab
|
||||
except ImportError:
|
||||
# pre 4.5.0
|
||||
from ipapython.ipautil import kinit_password, kinit_keytab
|
||||
except ImportError as _err:
|
||||
MODULE_IMPORT_ERROR = str(_err)
|
||||
else:
|
||||
|
||||
@@ -68,7 +68,8 @@ RETURN = '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
setup_logging, check_imports, options, configure_automount
|
||||
setup_logging, check_imports, options, configure_automount, sysrestore,
|
||||
paths, getargspec
|
||||
)
|
||||
|
||||
|
||||
@@ -94,10 +95,23 @@ def main():
|
||||
options.automount_location = module.params.get('automount_location')
|
||||
options.location = options.automount_location
|
||||
|
||||
changed = False
|
||||
if options.automount_location:
|
||||
configure_automount(options)
|
||||
changed = True
|
||||
argspec = getargspec(configure_automount)
|
||||
if len(argspec.args) > 1:
|
||||
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
|
||||
statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE)
|
||||
|
||||
module.exit_json(changed=True)
|
||||
configure_automount(options, statestore)
|
||||
|
||||
# Reload the state as automount install may have modified it
|
||||
fstore._load()
|
||||
statestore._load()
|
||||
else:
|
||||
configure_automount(options)
|
||||
|
||||
module.exit_json(changed=changed)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -152,6 +152,11 @@ options:
|
||||
The dist of nss_ldap or nss-pam-ldapd files if sssd is disabled
|
||||
required: yes
|
||||
type: dict
|
||||
selinux_works:
|
||||
description: True if selinux status check passed
|
||||
required: false
|
||||
type: bool
|
||||
default: false
|
||||
krb_name:
|
||||
description: The krb5 config file name
|
||||
type: str
|
||||
@@ -189,7 +194,7 @@ from ansible.module_utils.ansible_ipa_client import (
|
||||
CalledProcessError, tasks, client_dns, services,
|
||||
update_ssh_keys, save_state, configure_ldap_conf, configure_nslcd_conf,
|
||||
configure_openldap_conf, hardcode_ldap_server, getargspec, NUM_VERSION,
|
||||
serialization
|
||||
serialization, configure_selinux_for_client
|
||||
)
|
||||
|
||||
|
||||
@@ -224,6 +229,7 @@ def main():
|
||||
no_dns_sshfp=dict(required=False, type='bool', default=False),
|
||||
nosssd_files=dict(required=True, type='dict'),
|
||||
krb_name=dict(required=True, type='str'),
|
||||
selinux_works=dict(required=False, type='bool', default=False),
|
||||
),
|
||||
supports_check_mode=False,
|
||||
)
|
||||
@@ -274,6 +280,7 @@ def main():
|
||||
options.sssd = not options.no_sssd
|
||||
options.no_ac = False
|
||||
nosssd_files = module.params.get('nosssd_files')
|
||||
selinux_works = module.params.get('selinux_works')
|
||||
krb_name = module.params.get('krb_name')
|
||||
os.environ['KRB5_CONFIG'] = krb_name
|
||||
|
||||
@@ -474,6 +481,9 @@ def main():
|
||||
logger.info("%s enabled", "SSSD" if options.sssd else "LDAP")
|
||||
|
||||
if options.sssd:
|
||||
if selinux_works and configure_selinux_for_client is not None:
|
||||
configure_selinux_for_client(statestore)
|
||||
|
||||
sssd = services.service('sssd', api)
|
||||
try:
|
||||
sssd.restart()
|
||||
|
||||
@@ -226,6 +226,10 @@ nosssd_files:
|
||||
returned: always
|
||||
type: list
|
||||
elements: str
|
||||
selinux_works:
|
||||
description: True if the selinux status check passed.
|
||||
returned: always
|
||||
type: bool
|
||||
'''
|
||||
|
||||
import os
|
||||
@@ -495,6 +499,8 @@ def main():
|
||||
# not installer.no_krb5_offline_passwords
|
||||
installer.sssd = not installer.no_sssd
|
||||
|
||||
selinux_works = False
|
||||
|
||||
try:
|
||||
|
||||
# client
|
||||
@@ -529,7 +535,7 @@ def main():
|
||||
"You must be root to run ipa-client-install.",
|
||||
rval=CLIENT_INSTALL_ERROR)
|
||||
|
||||
tasks.check_selinux_status()
|
||||
selinux_works = tasks.check_selinux_status()
|
||||
|
||||
# if is_ipa_client_installed(fstore, on_master=options.on_master):
|
||||
# logger.error("IPA client is already configured on this system.")
|
||||
@@ -971,7 +977,8 @@ def main():
|
||||
ntp_pool=options.ntp_pool,
|
||||
client_already_configured=client_already_configured,
|
||||
ipa_python_version=IPA_PYTHON_VERSION,
|
||||
nosssd_files=nosssd_files)
|
||||
nosssd_files=nosssd_files,
|
||||
selinux_works=selinux_works)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -6,7 +6,7 @@ galaxy_info:
|
||||
description: A role to join a machine to an IPA domain
|
||||
company: Red Hat, Inc
|
||||
license: GPLv3
|
||||
min_ansible_version: "2.13"
|
||||
min_ansible_version: "2.15"
|
||||
platforms:
|
||||
- name: Fedora
|
||||
versions:
|
||||
|
||||
@@ -46,7 +46,8 @@ __all__ = ["gssapi", "version", "ipadiscovery", "api", "errors", "x509",
|
||||
"configure_nslcd_conf", "configure_ssh_config",
|
||||
"configure_sshd_config", "configure_automount",
|
||||
"configure_firefox", "sync_time", "check_ldap_conf",
|
||||
"sssd_enable_ifp", "getargspec", "paths", "options",
|
||||
"sssd_enable_ifp", "configure_selinux_for_client",
|
||||
"getargspec", "paths", "options",
|
||||
"IPA_PYTHON_VERSION", "NUM_VERSION", "certdb", "get_ca_cert",
|
||||
"ipalib", "logger", "ipautil", "installer"]
|
||||
|
||||
@@ -172,9 +173,13 @@ try:
|
||||
ipa_generate_password
|
||||
from ipapython.dn import DN
|
||||
try:
|
||||
from ipalib.install.kinit import kinit_keytab, kinit_password
|
||||
from ipalib.kinit import kinit_password, kinit_keytab
|
||||
except ImportError:
|
||||
from ipapython.ipautil import kinit_keytab, kinit_password
|
||||
try:
|
||||
from ipalib.install.kinit import kinit_keytab, kinit_password
|
||||
except ImportError:
|
||||
# pre 4.5.0
|
||||
from ipapython.ipautil import kinit_keytab, kinit_password
|
||||
from ipapython.ipa_log_manager import standard_logging_setup
|
||||
from gssapi.exceptions import GSSError
|
||||
try:
|
||||
@@ -302,6 +307,11 @@ try:
|
||||
except ImportError:
|
||||
sssd_enable_ifp = None
|
||||
|
||||
try:
|
||||
from ipaclient.install.client import configure_selinux_for_client
|
||||
except ImportError:
|
||||
configure_selinux_for_client = None
|
||||
|
||||
logger = logging.getLogger("ipa-client-install")
|
||||
root_logger = logger
|
||||
|
||||
|
||||
@@ -166,18 +166,19 @@
|
||||
register: result_ipaclient_get_otp
|
||||
delegate_to: "{{ result_ipaclient_test.servers[0] }}"
|
||||
|
||||
- name: Install - Report error for OTP generation
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ result_ipaclient_get_otp.msg }}"
|
||||
when: result_ipaclient_get_otp is failed
|
||||
failed_when: yes
|
||||
|
||||
- name: Install - Store the previously obtained OTP
|
||||
no_log: yes
|
||||
ansible.builtin.set_fact:
|
||||
ipaadmin_orig_password: "{{ ipaadmin_password | 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:
|
||||
msg: "{{ result_ipaclient_get_otp.msg }}"
|
||||
when: result_ipaclient_get_otp is failed
|
||||
failed_when: yes
|
||||
|
||||
always:
|
||||
- name: Install - Remove keytab temporary file
|
||||
ansible.builtin.file:
|
||||
@@ -383,6 +384,7 @@
|
||||
| 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 }}"
|
||||
krb_name: "{{ result_ipaclient_temp_krb5.krb_name }}"
|
||||
|
||||
- name: Install - Configure SSH and SSHD
|
||||
@@ -397,7 +399,7 @@
|
||||
ipaclient_setup_automount:
|
||||
servers: "{{ result_ipaclient_test.servers }}"
|
||||
sssd: "{{ result_ipaclient_test.sssd }}"
|
||||
automount_location: "{{ ipaautomount_location | default(omit) }}"
|
||||
automount_location: "{{ ipaclient_automount_location | default(omit) }}"
|
||||
|
||||
- name: Install - Configure firefox
|
||||
ipaclient_setup_firefox:
|
||||
|
||||
@@ -36,7 +36,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.13+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -139,7 +139,7 @@ def main():
|
||||
conn.connect(ccache=installer._ccache)
|
||||
remote_api.Command['hostgroup_add_member'](
|
||||
u'ipaservers',
|
||||
host=[unicode(api.env.host)],
|
||||
host=[unicode(api.env.host)], # pylint: disable=W0012,E0606
|
||||
)
|
||||
finally:
|
||||
if conn.isconnected():
|
||||
|
||||
@@ -51,6 +51,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
domain:
|
||||
description: Primary DNS domain of the IPA deployment
|
||||
type: str
|
||||
@@ -70,6 +71,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
no_host_dns:
|
||||
description: Do not use DNS for hostname lookup during installation
|
||||
type: bool
|
||||
@@ -97,6 +99,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
force_join:
|
||||
description: Force client enrollment even if already enrolled
|
||||
type: bool
|
||||
|
||||
@@ -90,7 +90,7 @@ from ansible.module_utils.ansible_ipa_replica import (
|
||||
check_imports, AnsibleModuleLog, setup_logging, installer, DN, paths,
|
||||
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
|
||||
gen_ReplicaConfig, gen_remote_api, api, redirect_stdout, service,
|
||||
find_providing_servers, services
|
||||
find_providing_servers, services, clean_up_hsm_nicknames
|
||||
)
|
||||
|
||||
|
||||
@@ -168,6 +168,9 @@ def main():
|
||||
# Everything installed properly, activate ipa service.
|
||||
services.knownservices.ipa.enable()
|
||||
|
||||
if options.setup_ca and clean_up_hsm_nicknames is not None:
|
||||
clean_up_hsm_nicknames(api)
|
||||
|
||||
# Print a warning if CA role is only installed on one server
|
||||
if len(ca_servers) == 1:
|
||||
msg = u'''
|
||||
|
||||
@@ -51,6 +51,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
domain:
|
||||
description: Primary DNS domain of the IPA deployment
|
||||
type: str
|
||||
@@ -70,6 +71,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
no_host_dns:
|
||||
description: Do not use DNS for hostname lookup during installation
|
||||
type: bool
|
||||
@@ -97,6 +99,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
force_join:
|
||||
description: Force client enrollment even if already enrolled
|
||||
type: bool
|
||||
@@ -156,6 +159,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
'''
|
||||
@@ -333,9 +337,7 @@ def main():
|
||||
|
||||
# done #
|
||||
|
||||
ansible_module.exit_json(changed=True,
|
||||
config_master_host_name=config.master_host_name,
|
||||
config_ca_host_name=config.ca_host_name)
|
||||
ansible_module.exit_json(changed=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -53,6 +53,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
domain:
|
||||
description: Primary DNS domain of the IPA deployment
|
||||
type: str
|
||||
@@ -77,6 +78,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
no_host_dns:
|
||||
description: Do not use DNS for hostname lookup during installation
|
||||
type: bool
|
||||
@@ -104,6 +106,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
dirsrv_cert_name:
|
||||
description: Name of the Directory Server SSL certificate to install
|
||||
type: str
|
||||
@@ -118,6 +121,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
http_cert_name:
|
||||
description: Name of the Apache Server SSL certificate to install
|
||||
type: str
|
||||
@@ -132,6 +136,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
pkinit_cert_name:
|
||||
description: Name of the Kerberos KDC SSL certificate to install
|
||||
type: str
|
||||
@@ -182,6 +187,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
no_reverse:
|
||||
description: Do not create new reverse DNS zone
|
||||
type: bool
|
||||
@@ -197,6 +203,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
no_forwarders:
|
||||
description: Do not add any DNS forwarders, use root servers instead
|
||||
type: bool
|
||||
@@ -250,6 +257,10 @@ options:
|
||||
type: bool
|
||||
default: no
|
||||
required: no
|
||||
ipa_client_installed:
|
||||
description: Was client configured already
|
||||
type: bool
|
||||
required: yes
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
'''
|
||||
@@ -275,7 +286,8 @@ from ansible.module_utils.ansible_ipa_replica import (
|
||||
check_domain_level_is_supported, errors, ScriptError, setup_logging,
|
||||
logger, check_dns_resolution, service, find_providing_server, ca, kra,
|
||||
dns, no_matching_interface_for_ip_address_warning, adtrust,
|
||||
constants, api, redirect_stdout, replica_conn_check, tasks
|
||||
constants, api, redirect_stdout, replica_conn_check, tasks,
|
||||
install_ca_cert
|
||||
)
|
||||
from ansible.module_utils import six
|
||||
|
||||
@@ -353,6 +365,7 @@ def main():
|
||||
skip_conncheck=dict(required=False, type='bool'),
|
||||
sid_generation_always=dict(required=False, type='bool',
|
||||
default=False),
|
||||
ipa_client_installed=dict(required=True, type='bool'),
|
||||
),
|
||||
supports_check_mode=False,
|
||||
)
|
||||
@@ -436,6 +449,7 @@ def main():
|
||||
# options._random_serial_numbers is generated by ca.install_check and
|
||||
# later used by ca.install in the _setup_ca module.
|
||||
options._random_serial_numbers = False
|
||||
ipa_client_installed = ansible_module.params.get('ipa_client_installed')
|
||||
|
||||
# init #
|
||||
|
||||
@@ -601,10 +615,20 @@ def main():
|
||||
ansible_log.debug("-- CA_CRT --")
|
||||
|
||||
cafile = paths.IPA_CA_CRT
|
||||
if not os.path.isfile(cafile):
|
||||
ansible_module.fail_json(
|
||||
msg="CA cert file is not available! Please reinstall"
|
||||
"the client and try again.")
|
||||
if install_ca_cert is not None:
|
||||
if not os.path.isfile(cafile):
|
||||
ansible_module.fail_json(
|
||||
msg="CA cert file is not available! Please reinstall"
|
||||
"the client and try again.")
|
||||
else:
|
||||
if ipa_client_installed:
|
||||
# host was already an IPA client, refresh client cert stores to
|
||||
# ensure we have up to date CA certs.
|
||||
try:
|
||||
ipautil.run([paths.IPA_CERTUPDATE])
|
||||
except ipautil.CalledProcessError:
|
||||
ansible_module.fail_json(
|
||||
msg="ipa-certupdate failed to refresh certs.")
|
||||
|
||||
ansible_log.debug("-- REMOTE_API --")
|
||||
|
||||
@@ -658,7 +682,7 @@ def main():
|
||||
# Check authorization
|
||||
result = remote_api.Command['hostgroup_find'](
|
||||
cn=u'ipaservers',
|
||||
host=[unicode(api.env.host)]
|
||||
host=[unicode(api.env.host)] # pylint: disable=W0012,E0606
|
||||
)['result']
|
||||
add_to_ipaservers = not result
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
'''
|
||||
|
||||
@@ -61,6 +61,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
forward_policy:
|
||||
description: DNS forwarding policy for global forwarders
|
||||
type: str
|
||||
|
||||
@@ -51,6 +51,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
domain:
|
||||
description: Primary DNS domain of the IPA deployment
|
||||
type: str
|
||||
@@ -70,6 +71,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
no_host_dns:
|
||||
description: Do not use DNS for hostname lookup during installation
|
||||
type: bool
|
||||
@@ -108,6 +110,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
force_join:
|
||||
description: Force client enrollment even if already enrolled
|
||||
type: bool
|
||||
@@ -176,6 +179,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
'''
|
||||
|
||||
@@ -51,6 +51,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
domain:
|
||||
description: Primary DNS domain of the IPA deployment
|
||||
type: str
|
||||
@@ -70,6 +71,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
no_host_dns:
|
||||
description: Do not use DNS for hostname lookup during installation
|
||||
type: bool
|
||||
@@ -101,6 +103,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
force_join:
|
||||
description: Force client enrollment even if already enrolled
|
||||
type: bool
|
||||
|
||||
@@ -42,6 +42,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
domain:
|
||||
description: Primary DNS domain of the IPA deployment
|
||||
type: str
|
||||
@@ -51,6 +52,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
realm:
|
||||
description: Kerberos realm name of the IPA deployment
|
||||
type: str
|
||||
@@ -66,6 +68,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
hidden_replica:
|
||||
description: Install a hidden replica
|
||||
type: bool
|
||||
@@ -112,18 +115,21 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
http_cert_files:
|
||||
description:
|
||||
File containing the Apache Server SSL certificate and private key
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
pkinit_cert_files:
|
||||
description:
|
||||
File containing the Kerberos KDC SSL certificate and private key
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
no_ntp:
|
||||
description: Do not configure ntp
|
||||
type: bool
|
||||
@@ -134,6 +140,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
ntp_pool:
|
||||
description: ntp server pool to use
|
||||
type: str
|
||||
@@ -153,6 +160,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
no_forwarders:
|
||||
description: Do not add any DNS forwarders, use root servers instead
|
||||
type: bool
|
||||
@@ -191,7 +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
|
||||
IPA_PYTHON_VERSION, getargspec, adtrustinstance, install_ca_cert
|
||||
)
|
||||
|
||||
|
||||
@@ -542,7 +550,8 @@ def main():
|
||||
# additional
|
||||
client_enrolled=client_enrolled,
|
||||
change_master_for_certmonger=change_master_for_certmonger,
|
||||
sid_generation_always=sid_generation_always
|
||||
sid_generation_always=sid_generation_always,
|
||||
install_ca_certs=install_ca_cert is not None
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ galaxy_info:
|
||||
description: A role to setup an IPA domain replica
|
||||
company: Red Hat, Inc
|
||||
license: GPLv3
|
||||
min_ansible_version: "2.13"
|
||||
min_ansible_version: "2.15"
|
||||
platforms:
|
||||
- name: Fedora
|
||||
versions:
|
||||
|
||||
@@ -49,7 +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"]
|
||||
"ReplicaConfig", "create_api", "clean_up_hsm_nicknames"]
|
||||
|
||||
import sys
|
||||
import logging
|
||||
@@ -104,7 +104,10 @@ try:
|
||||
from ipaclient.install.ipachangeconf import IPAChangeConf
|
||||
from ipalib.install import certstore, sysrestore
|
||||
from ipapython.ipautil import ipa_generate_password
|
||||
from ipalib.install.kinit import kinit_keytab
|
||||
try:
|
||||
from ipalib.kinit import kinit_keytab
|
||||
except ImportError:
|
||||
from ipalib.install.kinit import kinit_keytab
|
||||
from ipapython import ipaldap, ipautil, kernel_keyring
|
||||
from ipapython.certdb import IPA_CA_TRUST_FLAGS, \
|
||||
EXTERNAL_CA_TRUST_FLAGS
|
||||
@@ -141,7 +144,7 @@ try:
|
||||
from ipaserver.install.replication import (
|
||||
ReplicationManager, replica_conn_check)
|
||||
from ipaserver.install.server.replicainstall import (
|
||||
make_pkcs12_info, install_replica_ds, install_krb, install_ca_cert,
|
||||
make_pkcs12_info, install_replica_ds, install_krb,
|
||||
install_http, install_dns_records, create_ipa_conf, check_dirsrv,
|
||||
check_dns_resolution, configure_certmonger,
|
||||
remove_replica_info_dir,
|
||||
@@ -154,6 +157,16 @@ try:
|
||||
# ensure_enrolled,
|
||||
promotion_check_ipa_domain
|
||||
)
|
||||
try:
|
||||
from ipaserver.install.server.replicainstall import \
|
||||
install_ca_cert
|
||||
except ImportError:
|
||||
install_ca_cert = None
|
||||
try:
|
||||
from ipaserver.install.server.replicainstall import \
|
||||
clean_up_hsm_nicknames
|
||||
except ImportError:
|
||||
clean_up_hsm_nicknames = None
|
||||
import SSSDConfig
|
||||
from subprocess import CalledProcessError
|
||||
|
||||
|
||||
@@ -57,6 +57,11 @@
|
||||
ipareplica_servers: "{{ groups['ipaservers'] | list }}"
|
||||
when: groups.ipaservers is defined and ipareplica_servers is not defined
|
||||
|
||||
- name: Install - Set ipareplica_servers from cluster inventory
|
||||
ansible.builtin.set_fact:
|
||||
ipareplica_servers: "{{ groups['ipaserver'] | list }}"
|
||||
when: ipareplica_servers is not defined and groups.ipaserver is defined
|
||||
|
||||
- name: Install - Set default principal if no keytab is given
|
||||
ansible.builtin.set_fact:
|
||||
ipaadmin_principal: admin
|
||||
@@ -204,6 +209,7 @@
|
||||
server: "{{ result_ipareplica_test.server }}"
|
||||
skip_conncheck: "{{ ipareplica_skip_conncheck }}"
|
||||
sid_generation_always: "{{ result_ipareplica_test.sid_generation_always }}"
|
||||
ipa_client_installed: "{{ result_ipareplica_test.client_enrolled }}"
|
||||
register: result_ipareplica_prepare
|
||||
|
||||
- name: Install - Add to ipaservers
|
||||
@@ -271,6 +277,7 @@
|
||||
config_ca_host_name: "{{ result_ipareplica_prepare.config_ca_host_name }}"
|
||||
config_ips: "{{ result_ipareplica_prepare.config_ips }}"
|
||||
register: result_ipareplica_install_ca_certs
|
||||
when: result_ipareplica_test.install_ca_certs
|
||||
|
||||
- name: Install - Setup DS
|
||||
ipareplica_setup_ds:
|
||||
@@ -307,7 +314,7 @@
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
config_ca_host_name: "{{ result_ipareplica_prepare.config_ca_host_name }}"
|
||||
config_ips: "{{ result_ipareplica_prepare.config_ips }}"
|
||||
register: result_ipareplica_setup_ds
|
||||
@@ -334,7 +341,7 @@
|
||||
### additional ###
|
||||
server: "{{ result_ipareplica_test.server }}"
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
config_ca_host_name: "{{ result_ipareplica_prepare.config_ca_host_name }}"
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
installer_ccache: "{{ result_ipareplica_prepare.installer_ccache }}"
|
||||
@@ -357,7 +364,7 @@
|
||||
subject_base: "{{ result_ipareplica_prepare.subject_base }}"
|
||||
### additional ###
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
_pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
@@ -388,7 +395,7 @@
|
||||
### additional ###
|
||||
server: "{{ result_ipareplica_test.server }}"
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
config_ca_host_name: "{{ result_ipareplica_prepare.config_ca_host_name }}"
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
installer_ccache: "{{ result_ipareplica_prepare.installer_ccache }}"
|
||||
@@ -401,7 +408,7 @@
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
|
||||
master:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
when: result_ipareplica_test.change_master_for_certmonger
|
||||
|
||||
- name: Install - DS enable SSL
|
||||
@@ -415,7 +422,7 @@
|
||||
subject_base: "{{ result_ipareplica_prepare.subject_base }}"
|
||||
### additional ###
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
_ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
|
||||
_ca_file: "{{ result_ipareplica_prepare._ca_file }}"
|
||||
@@ -436,7 +443,7 @@
|
||||
subject_base: "{{ result_ipareplica_prepare.subject_base }}"
|
||||
### additional ###
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
config_ca_host_name: "{{ result_ipareplica_prepare.config_ca_host_name }}"
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
_ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
|
||||
@@ -468,7 +475,7 @@
|
||||
### additional ###
|
||||
server: "{{ result_ipareplica_test.server }}"
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
config_ca_host_name: "{{ result_ipareplica_prepare.config_ca_host_name }}"
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
installer_ccache: "{{ result_ipareplica_prepare.installer_ccache }}"
|
||||
@@ -493,7 +500,7 @@
|
||||
subject_base: "{{ result_ipareplica_prepare.subject_base }}"
|
||||
### additional ###
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
_ca_file: "{{ result_ipareplica_prepare._ca_file }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
@@ -544,9 +551,9 @@
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
config_ca_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_ca_host_name }}"
|
||||
"{{ result_ipareplica_prepare.config_ca_host_name }}"
|
||||
config_ips: "{{ result_ipareplica_prepare.config_ips }}"
|
||||
when: result_ipareplica_prepare._ca_enabled
|
||||
|
||||
@@ -560,7 +567,7 @@
|
||||
subject_base: "{{ result_ipareplica_prepare.subject_base }}"
|
||||
### additional ###
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
_ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
|
||||
_ca_file: "{{ result_ipareplica_prepare._ca_file }}"
|
||||
@@ -580,7 +587,7 @@
|
||||
subject_base: "{{ result_ipareplica_prepare.subject_base }}"
|
||||
### additional ###
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
_ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
|
||||
_ca_file: "{{ result_ipareplica_prepare._ca_file }}"
|
||||
@@ -640,7 +647,7 @@
|
||||
subject_base: "{{ result_ipareplica_prepare.subject_base }}"
|
||||
### additional ###
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
_ca_file: "{{ result_ipareplica_prepare._ca_file }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user