mirror of
https://github.com/freeipa/ansible-freeipa.git
synced 2026-03-27 05:43:05 +00:00
Compare commits
343 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 | ||
|
|
1930d8c8be | ||
|
|
1837ee662c | ||
|
|
26e171df79 | ||
|
|
01440e3c04 | ||
|
|
2426e04c22 | ||
|
|
92e44f6a6c | ||
|
|
16c8ee87e9 | ||
|
|
3109e9d1bc | ||
|
|
b457de545d | ||
|
|
b22bf4dfb9 | ||
|
|
f1a6f44477 | ||
|
|
1dbe19cefb | ||
|
|
7982fad342 | ||
|
|
212719496c | ||
|
|
3de6f9146e | ||
|
|
48f2ef88a4 | ||
|
|
6845acd596 | ||
|
|
f012da22ce | ||
|
|
ba7bf0f6cd | ||
|
|
fe2d17e4df | ||
|
|
319a0d3d86 | ||
|
|
c71a2b33dd | ||
|
|
02223dfb67 | ||
|
|
5731a1539b | ||
|
|
ee7354230b | ||
|
|
4bb40f3397 | ||
|
|
55b8729c52 | ||
|
|
539ace413d | ||
|
|
0c20b34d28 | ||
|
|
f9ff41320f | ||
|
|
69c6b4d644 | ||
|
|
b63716b724 | ||
|
|
3cf138674b | ||
|
|
12e0d110f6 | ||
|
|
34654d1090 | ||
|
|
72d3ab8e04 | ||
|
|
fb75aed663 | ||
|
|
6f5bb9eebf | ||
|
|
e5b2c122ce | ||
|
|
c0692e1746 | ||
|
|
2d079c8eec | ||
|
|
b70a1ecf61 | ||
|
|
7cb5e481e5 | ||
|
|
60593b7dd3 | ||
|
|
e84ed3b6ba | ||
|
|
6e1f9f1a72 | ||
|
|
46a307aaeb | ||
|
|
d8f8211a1c | ||
|
|
34daa992f5 | ||
|
|
07c1a5ee61 | ||
|
|
63d0272385 | ||
|
|
d0a8005a7f | ||
|
|
24efad73fa | ||
|
|
fd1352ad7e | ||
|
|
de38e8f0bc | ||
|
|
847ae2a374 | ||
|
|
bcee9aba92 | ||
|
|
c34c66fa79 | ||
|
|
0a3cd06c6e | ||
|
|
b5b22c3f7e | ||
|
|
7ee385ee02 | ||
|
|
7d9e4da9df | ||
|
|
0a20b5902d | ||
|
|
be9a2db404 | ||
|
|
ba4a360520 | ||
|
|
3534fcdce7 | ||
|
|
f0f21fc8aa | ||
|
|
5ed96eda05 | ||
|
|
cf779e43bb | ||
|
|
1a48a0fb63 | ||
|
|
ed3a0d5a1b | ||
|
|
d58b492f1d | ||
|
|
88d4a36e17 | ||
|
|
6fa8223662 | ||
|
|
c9e8656494 | ||
|
|
a791c6a0ca | ||
|
|
9cbccdade9 | ||
|
|
42c07d6336 | ||
|
|
a728a8d43e | ||
|
|
bd3266e9f1 | ||
|
|
48063d2b3a | ||
|
|
5d08214516 | ||
|
|
ef0b7e80f0 | ||
|
|
a33fcf45f8 | ||
|
|
c4b273c896 | ||
|
|
62d34d0a22 | ||
|
|
3ed0c229c4 | ||
|
|
c089c010e6 | ||
|
|
cfbdd83a64 | ||
|
|
fef1bdcf8e | ||
|
|
411d363d91 | ||
|
|
1555132d85 | ||
|
|
57ad57dda3 | ||
|
|
dab64c7cf6 | ||
|
|
b7145bc2cc | ||
|
|
c9f1da5d6b | ||
|
|
f4070f6a30 | ||
|
|
ad9a03ece6 | ||
|
|
1bfe6888a4 | ||
|
|
51ddaa6491 | ||
|
|
f56861cc15 | ||
|
|
c4de680497 | ||
|
|
7b2701b985 | ||
|
|
694c717829 | ||
|
|
083396e133 | ||
|
|
9a8a1db38f | ||
|
|
8f9c344bc1 | ||
|
|
067b683b81 | ||
|
|
51f64e4393 | ||
|
|
45700bc02b | ||
|
|
d04a12e522 | ||
|
|
4e9ec11b23 | ||
|
|
2d93051101 | ||
|
|
1a7b279d78 | ||
|
|
be228d1df3 | ||
|
|
ce95c638be | ||
|
|
876f39a6c5 | ||
|
|
950840e050 | ||
|
|
87e1edf575 | ||
|
|
09250cb2c5 | ||
|
|
872c9e4cb2 | ||
|
|
efe9c68600 | ||
|
|
0d9873b81c | ||
|
|
5b91703bd7 | ||
|
|
180afd7586 | ||
|
|
7f16914032 | ||
|
|
306522acd8 | ||
|
|
a155324188 | ||
|
|
8ec5b1fe21 | ||
|
|
316255d524 | ||
|
|
36b7a18e40 | ||
|
|
a32fcb3765 | ||
|
|
2d4cad6c1b | ||
|
|
a4b8e10a40 | ||
|
|
98681bd4d2 | ||
|
|
2882e2426a | ||
|
|
f056775d95 | ||
|
|
ad5450cd6f | ||
|
|
e75d82131d | ||
|
|
99e468ad60 | ||
|
|
3cc111782c | ||
|
|
b429b4495e | ||
|
|
0f99ef2199 | ||
|
|
1c8f1c28e1 | ||
|
|
47d5211185 | ||
|
|
4a18ad03c8 | ||
|
|
966797dbee | ||
|
|
892c0dd6f0 | ||
|
|
645a234d92 | ||
|
|
5cbc8b7ada | ||
|
|
5e5fbd87bf | ||
|
|
35ded3bf53 | ||
|
|
209c6365ea | ||
|
|
a69446021b | ||
|
|
b861a61857 | ||
|
|
6faff2ac11 | ||
|
|
82c0161245 | ||
|
|
ecab42b9f5 | ||
|
|
183ea7fd79 | ||
|
|
a4087a755b | ||
|
|
fb3ff6d63d | ||
|
|
ee92d99243 | ||
|
|
a649a8dfe1 | ||
|
|
80abf635c3 | ||
|
|
24e05d1df4 | ||
|
|
065e902182 | ||
|
|
96f5f5c86e | ||
|
|
476d9d5057 | ||
|
|
049024bbb2 | ||
|
|
ec03ad2bf9 | ||
|
|
64c43c1ec0 | ||
|
|
b1eb32993d | ||
|
|
2ee7139560 | ||
|
|
10d072a8c4 | ||
|
|
0ec89eb53c | ||
|
|
cf27a98c61 | ||
|
|
fd3e87771a | ||
|
|
e03752955f | ||
|
|
338df6e60e | ||
|
|
3f3e495ab3 | ||
|
|
b05aec98c5 | ||
|
|
867f7ed520 | ||
|
|
3cc17a43aa | ||
|
|
2b0b7db086 | ||
|
|
87afc56ee6 | ||
|
|
61caa57801 | ||
|
|
6b5acd9b0c | ||
|
|
78b5e66da4 | ||
|
|
f6c376a68f | ||
|
|
691fbd083e | ||
|
|
77cd20bc10 | ||
|
|
16ce5f21de | ||
|
|
dcf9c7d8ce | ||
|
|
c715d3aad2 | ||
|
|
0d1e9d3f49 | ||
|
|
b30ae1c9b5 | ||
|
|
bfeefaf454 | ||
|
|
0c23ae5b37 | ||
|
|
3b4367cf89 |
@@ -35,6 +35,7 @@ skip_list:
|
||||
- yaml # yamllint should be executed separately.
|
||||
- experimental # Do not run any experimental tests
|
||||
- name[template] # Allow Jinja templating inside task names
|
||||
- var-naming
|
||||
|
||||
use_default_rules: true
|
||||
|
||||
|
||||
4
.github/workflows/ansible-test.yml
vendored
4
.github/workflows/ansible-test.yml
vendored
@@ -8,10 +8,8 @@ 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: Install virtualenv using pip
|
||||
run: pip install virtualenv
|
||||
- name: Run ansible-test
|
||||
run: bash tests/sanity/sanity.sh
|
||||
|
||||
54
.github/workflows/docs.yml
vendored
54
.github/workflows/docs.yml
vendored
@@ -5,30 +5,13 @@ on:
|
||||
- pull_request
|
||||
jobs:
|
||||
check_docs_oldest_supported:
|
||||
name: Check Ansible Documentation with ansible-core 2.12.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v4.3.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install Ansible 2.12
|
||||
run: |
|
||||
python -m pip install "ansible-core >=2.12,<2.13"
|
||||
- name: Run ansible-doc-test
|
||||
run: |
|
||||
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
|
||||
|
||||
check_docs_previous:
|
||||
name: Check Ansible Documentation with ansible-core 2.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
|
||||
@@ -38,14 +21,14 @@ jobs:
|
||||
run: |
|
||||
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
|
||||
|
||||
check_docs_current:
|
||||
check_docs_previous:
|
||||
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
|
||||
@@ -55,14 +38,31 @@ jobs:
|
||||
run: |
|
||||
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
|
||||
|
||||
check_docs_current:
|
||||
name: Check Ansible Documentation with ansible-core 2.15.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install Ansible 2.15
|
||||
run: |
|
||||
python -m pip install "ansible-core >=2.15,<2.16"
|
||||
- name: Run ansible-doc-test
|
||||
run: |
|
||||
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
|
||||
|
||||
check_docs_ansible_latest:
|
||||
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
|
||||
|
||||
38
.github/workflows/lint.yml
vendored
38
.github/workflows/lint.yml
vendored
@@ -8,27 +8,27 @@ 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.14,<2.15" ansible-lint
|
||||
pip install "ansible-core>=2.16,<2.17" 'ansible-lint==6.22'
|
||||
utils/build-galaxy-release.sh -ki
|
||||
cd .galaxy-build
|
||||
ansible-lint
|
||||
ansible-lint --profile production --exclude tests/integration/ --exclude tests/unit/ --parseable --nocolor
|
||||
|
||||
yamllint:
|
||||
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,23 +68,23 @@ 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
|
||||
run: |
|
||||
pip install pylint==2.14.4 wrapt==1.14.0
|
||||
pip install 'pylint>=3.0'
|
||||
pylint plugins roles --disable=import-error
|
||||
|
||||
shellcheck:
|
||||
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
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,5 +1,11 @@
|
||||
*.pyc
|
||||
*.retry
|
||||
*.swp
|
||||
|
||||
# collection files
|
||||
freeipa-ansible_freeipa*.tar.gz
|
||||
redhat-rhel_idm*.tar.gz
|
||||
importer_result.json
|
||||
|
||||
# ignore virtual environments
|
||||
/.tox/
|
||||
|
||||
@@ -1,30 +1,40 @@
|
||||
---
|
||||
repos:
|
||||
- repo: https://github.com/ansible/ansible-lint.git
|
||||
rev: v6.6.1
|
||||
rev: v24.5.0
|
||||
hooks:
|
||||
- id: ansible-lint
|
||||
always_run: false
|
||||
pass_filenames: true
|
||||
files: \.(yaml|yml)$
|
||||
exclude: /env[^/]*.(yaml|yml)$
|
||||
entry: |
|
||||
env ANSIBLE_LIBRARY=./plugins/modules ANSIBLE_MODULE_UTILS=./plugins/module_utils ANSIBLE_DOC_FRAGMENT_PLUGINS=./plugins/doc_fragments ansible-lint
|
||||
entry: |-
|
||||
env
|
||||
ANSIBLE_LIBRARY=./plugins/modules
|
||||
ANSIBLE_MODULE_UTILS=./plugins/module_utils
|
||||
ANSIBLE_DOC_FRAGMENT_PLUGINS=./plugins/doc_fragments
|
||||
ansible-lint
|
||||
--offline
|
||||
--profile production
|
||||
--exclude tests/integration/
|
||||
--exclude tests/unit/
|
||||
--parseable
|
||||
--nocolor
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
rev: v1.28.0
|
||||
rev: v1.35.1
|
||||
hooks:
|
||||
- id: yamllint
|
||||
files: \.(yaml|yml)$
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 5.0.3
|
||||
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: v2.14.4
|
||||
rev: v3.2.2
|
||||
hooks:
|
||||
- id: pylint
|
||||
args:
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* 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.8+
|
||||
* 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.8+
|
||||
* 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.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
@@ -54,6 +54,21 @@ Example playbook to ensure presence of an automount map:
|
||||
desc: "this is a map for servers in the DMZ"
|
||||
```
|
||||
|
||||
Automount maps can contain a submount key, which defines a mount location within the map the references another map. On FreeIPA, this is known as an indirect map. An indirect automount map is equivalent to adding a proper automount key to a map, referencyng another map (this second map is the indirect map). Use `parent` and `mount` parameters to create an indirect automount map with ansible-freeipa, without the need to directly manage the automount keys.
|
||||
|
||||
Example playbook to ensure an indirect automount map is present:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to add an indirect automount map
|
||||
ipaautomountmap:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: auto.indirect
|
||||
location: DMZ
|
||||
parent: auto.DMZ
|
||||
mount: dmz_indirect
|
||||
```
|
||||
|
||||
Example playbook to ensure auto.DMZi is absent:
|
||||
|
||||
```yaml
|
||||
@@ -81,16 +96,14 @@ Variable | Description | Required
|
||||
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
|
||||
`name` \| `mapname` \| `map` \| `automountmapname` | Name of the map to manage | yes
|
||||
`location` \| `automountlocation` \| `automountlocationcn` | Location name. | yes
|
||||
`parentmap` | Parent map of the indirect map. Can only be used when creating new maps. Default: auto.master | no
|
||||
`mount` | Indirect map mount point, relative to parent map. | yes, if `parent` is used.
|
||||
`desc` \| `description` | Description of the map | yes
|
||||
`state` | The state to ensure. It can be one of `present`, or `absent`, default: `present`. | no
|
||||
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
Creation of indirect mount points are not supported.
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Chris Procter
|
||||
- Chris Procter
|
||||
- Rafael Jeffman
|
||||
|
||||
192
README-cert.md
Normal file
192
README-cert.md
Normal file
@@ -0,0 +1,192 @@
|
||||
Cert module
|
||||
============
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The cert module makes it possible to request, revoke and retrieve SSL certificates for hosts, services and users.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Certificate request
|
||||
* Certificate hold/release
|
||||
* Certificate revocation
|
||||
* Certificate retrieval
|
||||
|
||||
|
||||
Supported FreeIPA Versions
|
||||
--------------------------
|
||||
|
||||
FreeIPA versions 4.4.0 and up are supported by the ipacert module.
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.15+
|
||||
* Some tool to generate a certificate signing request (CSR) might be needed, like `openssl`.
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Example inventory file
|
||||
|
||||
```ini
|
||||
[ipaserver]
|
||||
ipaserver.test.local
|
||||
```
|
||||
|
||||
Example playbook to request a new certificate for a service:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Certificate request
|
||||
hosts: ipaserver
|
||||
|
||||
tasks:
|
||||
- name: Request a certificate for a web server
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
state: requested
|
||||
csr: |
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIGYMEwCAQAwGTEXMBUGA1UEAwwOZnJlZWlwYSBydWxlcyEwKjAFBgMrZXADIQBs
|
||||
HlqIr4b/XNK+K8QLJKIzfvuNK0buBhLz3LAzY7QDEqAAMAUGAytlcANBAF4oSCbA
|
||||
5aIPukCidnZJdr491G4LBE+URecYXsPknwYb+V+ONnf5ycZHyaFv+jkUBFGFeDgU
|
||||
SYaXm/gF8cDYjQI=
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
principal: HTTP/www.example.com
|
||||
register: cert
|
||||
```
|
||||
|
||||
Example playbook to revoke an existing certificate:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Revoke certificate
|
||||
hosts: ipaserver
|
||||
|
||||
tasks:
|
||||
- name Revoke a certificate
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
serial_number: 123456789
|
||||
reason: 5
|
||||
state: revoked
|
||||
```
|
||||
|
||||
When revoking a certificate a mnemonic can also be used to set the revocation reason:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Revoke certificate
|
||||
hosts: ipaserver
|
||||
|
||||
tasks:
|
||||
- name Revoke a certificate
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
serial_number: 123456789
|
||||
reason: cessationOfOperation
|
||||
state: revoked
|
||||
```
|
||||
|
||||
Example to hold a certificate (alias for revoking a certificate with reason `certificateHold (6)`):
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Hold a certificate
|
||||
hosts: ipaserver
|
||||
|
||||
tasks:
|
||||
- name: Hold certificate
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
serial_number: 0xAB1234
|
||||
state: held
|
||||
```
|
||||
|
||||
Example playbook to release hold of certificate (may be used with any revoked certificates, despite of the rovoke reason):
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Release hold
|
||||
hosts: ipaserver
|
||||
|
||||
tasks:
|
||||
- name: Take a revoked certificate off hold
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
serial_number: 0xAB1234
|
||||
state: released
|
||||
```
|
||||
|
||||
Example playbook to retrieve a certificate and save it to a file in the target node:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Retriev certificate
|
||||
hosts: ipaserver
|
||||
|
||||
tasks:
|
||||
- name: Retrieve a certificate and save it to file 'cert.pem'
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
certificate_out: cert.pem
|
||||
state: retrieved
|
||||
```
|
||||
|
||||
|
||||
ipacert
|
||||
-------
|
||||
|
||||
Variable | Description | Required
|
||||
-------- | ----------- | --------
|
||||
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
|
||||
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
|
||||
`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
|
||||
`ipaapi_ldap_cache` | Use LDAP cache for IPA connection. The bool setting defaults to yes. (bool) | no
|
||||
`csr` | X509 certificate signing request, in PEM format. | yes, if `state: requested`
|
||||
`principal` | Host/service/user principal for the certificate. | yes, if `state: requested`
|
||||
`add` \| `add_principal` | Automatically add the principal if it doesn't exist (service principals only). (bool) | no
|
||||
`profile_id` \| `profile` | Certificate Profile to use | no
|
||||
`ca` | Name of the issuing certificate authority. | no
|
||||
`chain` | Include certificate chain in output. (bool) | no
|
||||
`serial_number` | Certificate serial number. (int) | yes, if `state` is `retrieved`, `held`, `released` or `revoked`.
|
||||
`revocation_reason` \| `reason` | Reason for revoking the certificate. Use one of the reason strings, or the corresponding value: "unspecified" (0), "keyCompromise" (1), "cACompromise" (2), "affiliationChanged" (3), "superseded" (4), "cessationOfOperation" (5), "certificateHold" (6), "removeFromCRL" (8), "privilegeWithdrawn" (9), "aACompromise" (10) | yes, if `state: revoked`
|
||||
`certificate_out` | Write certificate (chain if `chain` is set) to this file, on the target node. | no
|
||||
`state` | The state to ensure. It can be one of `requested`, `held`, `released`, `revoked`, or `retrieved`. `held` is the same as revoke with reason "certificateHold" (6). `released` is the same as `cert-revoke-hold` on IPA CLI, releasing the hold status of a certificate. | yes
|
||||
|
||||
|
||||
Return Values
|
||||
=============
|
||||
|
||||
Values are returned only if `state` is `requested` or `retrieved` and if `certificate_out` is not defined.
|
||||
|
||||
Variable | Description | Returned When
|
||||
-------- | ----------- | -------------
|
||||
`certificate` | Certificate fields and data. (dict) <br>Options: | if `state` is `requested` or `retrieved` and if `certificate_out` is not defined
|
||||
| `certificate` - Issued X509 certificate in PEM encoding. Will include certificate chain if `chain: true`. (list) | always
|
||||
| `san_dnsname` - X509 Subject Alternative Name. | When DNSNames are present in the Subject Alternative Name extension of the issued certificate.
|
||||
| `issuer` - X509 distinguished name of issuer. | always
|
||||
| `subject` - X509 distinguished name of certificate subject. | always
|
||||
| `serial_number` - Serial number of the issued certificate. (int) | always
|
||||
| `revoked` - Revoked status of the certificate. (bool) | if certificate was revoked
|
||||
| `owner_user` - The username that owns the certificate. | if `state: retrieved` and certificate is owned by a user
|
||||
| `owner_host` - The host that owns the certificate. | if `state: retrieved` and certificate is owned by a host
|
||||
| `owner_service` - The service that owns the certificate. | if `state: retrieved` and certificate is owned by a service
|
||||
| `valid_not_before` - Time when issued certificate becomes valid, in GeneralizedTime format (YYYYMMDDHHMMSSZ) | always
|
||||
| `valid_not_after` - Time when issued certificate ceases to be valid, in GeneralizedTime format (YYYYMMDDHHMMSSZ) | always
|
||||
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Sam Morris
|
||||
Rafael Jeffman
|
||||
@@ -25,7 +25,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
@@ -145,7 +145,7 @@ Variable | Description | Required
|
||||
`selinuxusermaporder` \| `ipaselinuxusermaporder`| Set ordered list in increasing priority of SELinux users | no
|
||||
`selinuxusermapdefault`\| `ipaselinuxusermapdefault` | Set default SELinux user when no match is found in SELinux map rule | no
|
||||
`pac_type` \| `ipakrbauthzdata` | set default types of PAC supported for services (choices: `MS-PAC`, `PAD`, `nfs:NONE`). Use `""` to clear this variable. | no
|
||||
`user_auth_type` \| `ipauserauthtype` | set default types of supported user authentication (choices: `password`, `radius`, `otp`, `disabled`). Use `""` to clear this variable. | no
|
||||
`user_auth_type` \| `ipauserauthtype` | set default types of supported user authentication (choices: `password`, `radius`, `otp`, `pkinit`, `hardened`, `idp`, `disabled`, `""`). An additional check ensures that only types can be used that are supported by the IPA version. Use `""` to clear this variable. | no
|
||||
`domain_resolution_order` \| `ipadomainresolutionorder` | Set list of domains used for short name qualification | no
|
||||
`ca_renewal_master_server` \| `ipacarenewalmasterserver`| Renewal master for IPA certificate authority. | no
|
||||
`enable_sid` | New users and groups automatically get a SID assigned. Cannot be deactivated once activated. Requires IPA 4.9.8+. (bool) | no
|
||||
|
||||
@@ -23,7 +23,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* 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.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -23,7 +23,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* 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
|
||||
|
||||
152
README-group.md
152
README-group.md
@@ -8,6 +8,9 @@ The group module allows to ensure presence and absence of groups and members of
|
||||
|
||||
The group module is as compatible as possible to the Ansible upstream `ipa_group` module, but additionally offers to add users to a group and also to remove users from a group.
|
||||
|
||||
## Note
|
||||
Ensuring presence (adding) of several groups with mixed types (`external`, `nonposix` and `posix`) requires a fix in FreeIPA. The module implements a workaround to automatically use `client` context if the fix is not present in the target node FreeIPA and if more than one group is provided to the task using the `groups` parameter. If `ipaapi_context` is forced to be `server`, the module will fail in this case.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
@@ -26,7 +29,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
@@ -71,6 +74,101 @@ Example playbook to add groups:
|
||||
name: appops
|
||||
```
|
||||
|
||||
These three `ipagroup` module calls can be combined into one with the `groups` variable:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to handle groups
|
||||
hosts: ipaserver
|
||||
|
||||
tasks:
|
||||
- name: Ensure groups ops, sysops and appops are present
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: ops
|
||||
gidnumber: 1234
|
||||
- name: sysops
|
||||
user:
|
||||
- pinky
|
||||
- name: appops
|
||||
```
|
||||
|
||||
You can also alternatively use a json file containing the groups, here `groups_present.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "group1",
|
||||
"description": "description group1"
|
||||
},
|
||||
{
|
||||
"name": "group2",
|
||||
"description": "description group2"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
And ensure the presence of the groups with this example playbook:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Tests
|
||||
hosts: ipaserver
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: Include groups_present.json
|
||||
include_vars:
|
||||
file: groups_present.json
|
||||
|
||||
- name: Groups present
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
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
|
||||
@@ -112,11 +210,11 @@ Example playbook to add group members to a group:
|
||||
Example playbook to add members from a trusted realm to an external group:
|
||||
|
||||
```yaml
|
||||
--
|
||||
---
|
||||
- name: Playbook to handle groups.
|
||||
hosts: ipaserver
|
||||
became: true
|
||||
|
||||
|
||||
tasks:
|
||||
- name: Create an external group and add members from a trust to it.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
@@ -127,6 +225,24 @@ Example playbook to add members from a trusted realm to an external group:
|
||||
- WINIPA\\Developers
|
||||
```
|
||||
|
||||
Example playbook to add nonposix and external groups:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to add nonposix and external groups
|
||||
hosts: ipaserver
|
||||
|
||||
tasks:
|
||||
- name: Add nonposix group sysops and external group appops
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: sysops
|
||||
nonposix: true
|
||||
- name: appops
|
||||
external: true
|
||||
```
|
||||
|
||||
Example playbook to remove groups:
|
||||
|
||||
```yaml
|
||||
@@ -136,13 +252,29 @@ Example playbook to remove groups:
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
# Remove goups sysops, appops and ops
|
||||
# Remove groups sysops, appops and ops
|
||||
- ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: sysops,appops,ops
|
||||
state: absent
|
||||
```
|
||||
|
||||
Example playbook to ensure groups are absent:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to handle groups
|
||||
hosts: ipaserver
|
||||
|
||||
tasks:
|
||||
- name: Ensure groups ops and sysops are absent
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: ops
|
||||
- name: sysops
|
||||
state: absent
|
||||
```
|
||||
|
||||
Variables
|
||||
=========
|
||||
@@ -152,8 +284,10 @@ Variable | Description | Required
|
||||
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
|
||||
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
|
||||
`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
|
||||
`ipaapi_ldap_cache` | Use LDAP cache for IPA connection. The bool setting defaults to yes. (bool) | no
|
||||
`ipaapi_ldap_cache` | Use LDAP cache for IPA connection. The bool setting defaults to <br/>. (bool) | no
|
||||
`name` \| `cn` | The list of group name strings. | no
|
||||
`groups` | The list of group dicts. Each `groups` dict entry can contain group variables.<br>There is one required option in the `groups` dict:| no
|
||||
| `name` - The group name string of the entry. | yes
|
||||
`description` | The group description string. | no
|
||||
`gid` \| `gidnumber` | The GID integer. | no
|
||||
`posix` | Create a non-POSIX group or change a non-POSIX to a posix group. `nonposix`, `posix` and `external` are mutually exclusive. (bool) | no
|
||||
@@ -167,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.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -24,7 +24,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
@@ -335,7 +335,7 @@ Variable | Description | Required
|
||||
-------- | ----------- | --------
|
||||
`description` | The host description. | no
|
||||
`locality` | Host locality (e.g. "Baltimore, MD"). | no
|
||||
`location` \| `ns_host_location` | Host location (e.g. "Lab 2"). | no
|
||||
`location` \| `ns_host_location` | Host physical location hint (e.g. "Lab 2"). | no
|
||||
`platform` \| `ns_hardware_platform` | Host hardware platform (e.g. "Lenovo T61"). | no
|
||||
`os` \| `ns_os_version` | Host operating system and version (e.g. "Fedora 9"). | no
|
||||
`password` \| `user_password` \| `userpassword` | Password used in bulk enrollment for absent or not enrolled hosts. | no
|
||||
@@ -354,7 +354,7 @@ Variable | Description | Required
|
||||
`mac_address` \| `macaddress` | List of hardware MAC addresses. | no
|
||||
`sshpubkey` \| `ipasshpubkey` | List of SSH public keys | no
|
||||
`userclass` \| `class` | Host category (semantics placed on this attribute are for local interpretation) | no
|
||||
`auth_ind` \| `krbprincipalauthind` | Defines an allow list for Authentication Indicators. Use 'otp' to allow OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA authentications. Use empty string to reset auth_ind to the initial value. Other values may be used for custom configurations. choices: ["radius", "otp", "pkinit", "hardened", ""] | no
|
||||
`auth_ind` \| `krbprincipalauthind` | Defines an allow list for Authentication Indicators. Use 'otp' to allow OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA authentications. Use empty string to reset auth_ind to the initial value. Other values may be used for custom configurations. An additional check ensures that only types can be used that are supported by the IPA version. Choices: ["radius", "otp", "pkinit", "hardened", "idp", ""] | no
|
||||
`requires_pre_auth` \| `ipakrbrequirespreauth` | Pre-authentication is required for the service (bool) | no
|
||||
`ok_as_delegate` \| `ipakrbokasdelegate` | Client credentials may be delegated to the service (bool) | no
|
||||
`ok_to_auth_as_delegate` \| `ipakrboktoauthasdelegate` | The service is allowed to authenticate on behalf of a client (bool) | no
|
||||
@@ -372,8 +372,8 @@ There are only return values if one or more random passwords have been generated
|
||||
Variable | Description | Returned When
|
||||
-------- | ----------- | -------------
|
||||
`host` | Host dict with random password. (dict) <br>Options: | If random is yes and host did not exist or update_password is yes
|
||||
| `randompassword` - The generated random password | If only one host is handled by the module
|
||||
| `name` - The host name of the host that got a new random password. (dict) <br> Options: <br> `randompassword` - The generated random password | If several hosts are handled by the module
|
||||
| `randompassword` - The generated random password | If only one host is handled by the module without using the `hosts` parameter.
|
||||
| `name` - The host name of the host that got a new random password. (dict) <br> Options: <br> `randompassword` - The generated random password | If several hosts are handled by the module with the `hosts` parameter.
|
||||
|
||||
|
||||
Authors
|
||||
|
||||
@@ -26,7 +26,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
233
README-idoverridegroup.md
Normal file
233
README-idoverridegroup.md
Normal file
@@ -0,0 +1,233 @@
|
||||
Idoverridegroup module
|
||||
============
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The idoverridegroup module allows to ensure presence and absence of idoverridegroups and idoverridegroup members.
|
||||
|
||||
|
||||
Use Cases
|
||||
---------
|
||||
|
||||
With idoverridegroup it is possible to manage group attributes within ID views. These attributes are for example the group name or gid.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Idoverridegroup management
|
||||
|
||||
|
||||
Supported FreeIPA Versions
|
||||
--------------------------
|
||||
|
||||
FreeIPA versions 4.4.0 and up are supported by the ipaidoverridegroup module.
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Example inventory file
|
||||
|
||||
```ini
|
||||
[ipaserver]
|
||||
ipaserver.test.local
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test group test_group is present in idview test_idview
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverridegroup
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test group test_group is present in idview test_idview.
|
||||
ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test group test_group is present in idview test_idview with description
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverridegroup
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test group test_group is present in idview test_idview with description
|
||||
ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
description: "test_group description"
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test group test_group is present in idview test_idview without description
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverridegroup
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test group test_group is present in idview test_idview without description
|
||||
ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
description: ""
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test group test_group is present in idview test_idview with internal name test_123_group
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverridegroup
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test group test_group is present in idview test_idview with internal name test_123_group
|
||||
ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
name: test_123_group
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test group test_group is present in idview test_idview without internal name
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverridegroup
|
||||
- name: Ensure test group test_group is present in idview test_idview without internal name
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
name: ""
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test group test_group is present in idview test_idview with gid 20001
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverridegroup
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test group test_group is present in idview test_idview with gid 20001
|
||||
ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
gid: 20001
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test group test_group is present in idview test_idview without gid
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverridegroup
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test group test_group is present in idview test_idview without gid
|
||||
ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
gid: ""
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test group test_group is present in idview test_idview with enabling falling back to AD DC LDAP when resolving AD trusted objects. (For two-way trusts only.)
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverridegroup
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test group test_group is present in idview test_idview with fallback_to_ldap enabled
|
||||
ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
fallback_to_ldap: true
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test group test_group is absent in idview test_idview
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverridegroup
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test group test_group is absent in idview test_idview
|
||||
ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
continue: true
|
||||
state: absent
|
||||
```
|
||||
|
||||
|
||||
Variables
|
||||
---------
|
||||
|
||||
Variable | Description | Required
|
||||
-------- | ----------- | --------
|
||||
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
|
||||
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
|
||||
`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
|
||||
`ipaapi_ldap_cache` | Use LDAP cache for IPA connection. The bool setting defaults to true. (bool) | no
|
||||
`idview` \| `idviewcn` | The doverridegroup idview string. | yes
|
||||
`anchor` \| `ipaanchoruuid` | The list of anchors to override. | yes
|
||||
`description` \| `desc` | Description | no
|
||||
`name` \| `group_name` \| `cn` | The group. | no
|
||||
`gid` \| `gidnumber` | Group ID Number (int or "") | no
|
||||
`fallback_to_ldap` | Allow falling back to AD DC LDAP when resolving AD trusted objects. For two-way trusts only. | no
|
||||
`delete_continue` \| `continue` | Continuous mode. Don't stop on errors. Valid only if `state` is `absent`. | no
|
||||
`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no
|
||||
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Thomas Woerner
|
||||
503
README-idoverrideuser.md
Normal file
503
README-idoverrideuser.md
Normal file
@@ -0,0 +1,503 @@
|
||||
Idoverrideuser module
|
||||
============
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The idoverrideuser module allows to ensure presence and absence of idoverrideusers and idoverrideuser members.
|
||||
|
||||
|
||||
Use Cases
|
||||
---------
|
||||
|
||||
With idoverrideuser it is possible to manage user attributes within ID views. These attributes are for example the login name, home directory, certificate for authentication or SSH keys.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Idoverrideuser management
|
||||
|
||||
|
||||
Supported FreeIPA Versions
|
||||
--------------------------
|
||||
|
||||
FreeIPA versions 4.4.0 and up are supported by the ipaidoverrideuser module.
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Example inventory file
|
||||
|
||||
```ini
|
||||
[ipaserver]
|
||||
ipaserver.test.local
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview.
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview with description
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview with description
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
description: "test_user description"
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview without description
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview without description
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
description: ""
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview with internal name test_123_user
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview with internal name test_123_user
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
name: test_123_user
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview without internal name
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview without internal name
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
name: ""
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview with uid 20001
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview with uid 20001
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
uid: 20001
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview without uid
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview without uid
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
uid: ""
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview with gecos "Gecos Test"
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview with gecos "Gecos Test"
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
gecos: Gecos Test
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview without gecos
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview without gecos
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
gecos: ""
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview with gidnumber
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview with gidnumber
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
gidnumber: 20001
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview without gidnumber
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview without gidnumber
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
gidnumber: ""
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview with homedir /Users
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview with homedir /Users
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
homedir: /Users
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview without homedir
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview without homedir
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
homedir: ""
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview with shell
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview with shell
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
shell: /bin/someshell
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview without shell
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview without shell
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
shell: ""
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview with sshpubkey
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview with sshpubkey
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
sshpubkey:
|
||||
- ssh-rsa AAAAB3NzaC1yc2EAAADAQABAAABgQCqmVDpEX5gnSjKuv97Ay ...
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview without sshpubkey
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview without sshpubkey
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
sshpubkey: []
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview with 1 certificate
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview with 1 certificate
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
certificate:
|
||||
- "{{ lookup('file', 'cert1.b64', rstrip=False) }}"
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview with 3 certificate members
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview with 3 certificate members
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
certificate:
|
||||
- "{{ lookup('file', 'cert1.b64', rstrip=False) }}"
|
||||
- "{{ lookup('file', 'cert2.b64', rstrip=False) }}"
|
||||
- "{{ lookup('file', 'cert3.b64', rstrip=False) }}"
|
||||
action: member
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview without 2 certificate members
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview without 2 certificate members
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
certificate:
|
||||
- "{{ lookup('file', 'cert2.b64', rstrip=False) }}"
|
||||
- "{{ lookup('file', 'cert3.b64', rstrip=False) }}"
|
||||
action: member
|
||||
state: absent
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview without certificates
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview without certificates
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
certificate: []
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is present in idview test_idview with enabling falling back to AD DC LDAP when resolving AD trusted objects. (For two-way trusts only.)
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview with fallback_to_ldap enabled
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
fallback_to_ldap: true
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure test user test_user is absent in idview test_idview
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is absent in idview test_idview
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
continue: true
|
||||
state: absent
|
||||
```
|
||||
|
||||
|
||||
Variables
|
||||
---------
|
||||
|
||||
Variable | Description | Required
|
||||
-------- | ----------- | --------
|
||||
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
|
||||
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
|
||||
`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
|
||||
`ipaapi_ldap_cache` | Use LDAP cache for IPA connection. The bool setting defaults to true. (bool) | no
|
||||
`idview` \| `idviewcn` | The doverrideuser idview string. | yes
|
||||
`anchor` \| `ipaanchoruuid` | The list of anchors to override. | yes
|
||||
`description` \| `desc` | Description | no
|
||||
`name` \| `login` | The user (internally uid) | no
|
||||
`uid` \| `uidnumber` | User ID Number (int or "") | no
|
||||
`gecos` | GECOS | no
|
||||
`gidnumber` | Group ID Number (int or ""). | no
|
||||
`homedir` \| `homedirectory` | Home directory. | no
|
||||
`shell` \| `loginshell` | Login shell. | no
|
||||
`sshpubkey` \| `ipasshpubkey` | List of SSH public keys. | no
|
||||
`certificate` \| `usercertificate` | List of Base-64 encoded user certificates. This variable can also be used with `action: member`. | no
|
||||
`fallback_to_ldap` | Allow falling back to AD DC LDAP when resolving AD trusted objects. For two-way trusts only. | no
|
||||
`delete_continue` \| `continue` | Continuous mode. Don't stop on errors. Valid only if `state` is `absent`. | no
|
||||
`nomembers` \| `no_members` | Suppress processing of membership attributes. Valid only if `state` is `absent`. | no
|
||||
`action` | Work on idoverrideuser or member level. It can be on of `member` or `idoverrideuser` and defaults to `idoverrideuser`. | no
|
||||
`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no
|
||||
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Thomas Woerner
|
||||
192
README-idp.md
Normal file
192
README-idp.md
Normal file
@@ -0,0 +1,192 @@
|
||||
Idp module
|
||||
============
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The idp module allows to ensure presence and absence of idps.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Idp management
|
||||
|
||||
|
||||
Supported FreeIPA Versions
|
||||
--------------------------
|
||||
|
||||
FreeIPA versions 4.4.0 and up are supported by the ipaidp module.
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Example inventory file
|
||||
|
||||
```ini
|
||||
[ipaserver]
|
||||
ipaserver.test.local
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure keycloak idp my-keycloak-idp is present:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage IPA idp.
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure keycloak idp my-keycloak-idp is present
|
||||
ipaidp:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: my-keycloak-idp
|
||||
provider: keycloak
|
||||
organization: main
|
||||
base_url: keycloak.idm.example.com:8443/auth
|
||||
client_id: my-client-id
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure keycloak idp my-keycloak-idp is absent:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage IPA idp.
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure keycloak idp my-keycloak-idp is absent
|
||||
ipaidp:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: my-keycloak-idp
|
||||
delete_continue: true
|
||||
state: absent
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure github idp my-github-idp is present:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage IPA idp.
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure github idp my-github-idp is present
|
||||
ipaidp:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: my-github-idp
|
||||
provider: github
|
||||
client_id: my-github-client-id
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure google idp my-google-idp is present using provider defaults without specifying provider:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage IPA idp.
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure google idp my-google-idp is present using provider defaults without specifying provider
|
||||
ipaidp:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: my-google-idp
|
||||
auth_uri: https://accounts.google.com/o/oauth2/auth
|
||||
dev_auth_uri: https://oauth2.googleapis.com/device/code
|
||||
token_uri: https://oauth2.googleapis.com/token
|
||||
keys_uri: https://www.googleapis.com/oauth2/v3/certs
|
||||
userinfo_uri: https://openidconnect.googleapis.com/v1/userinfo
|
||||
client_id: my-google-client-id
|
||||
scope: "openid email"
|
||||
idp_user_id: email
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure google idp my-google-idp is present using provider:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage IPA idp.
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure google idp my-google-idp is present using provider
|
||||
ipaidp:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: my-google-idp
|
||||
provider: google
|
||||
client_id: my-google-client-id
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure idps my-keycloak-idp, my-github-idp and my-google-idp are absent:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage IPA idp.
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure idps my-keycloak-idp, my-github-idp and my-google-idp are absent
|
||||
ipaidp:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name:
|
||||
- my-keycloak-idp
|
||||
- my-github-idp
|
||||
- my-google-idp
|
||||
delete_continue: true
|
||||
state: absent
|
||||
```
|
||||
|
||||
|
||||
Variables
|
||||
---------
|
||||
|
||||
Variable | Description | Required
|
||||
-------- | ----------- | --------
|
||||
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
|
||||
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
|
||||
`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
|
||||
`ipaapi_ldap_cache` | Use LDAP cache for IPA connection. The bool setting defaults to true. (bool) | false
|
||||
`name` \| `cn` | The list of idp name strings. | yes
|
||||
auth_uri \| ipaidpauthendpoint | OAuth 2.0 authorization endpoint string. | no
|
||||
dev_auth_uri \| ipaidpdevauthendpoint | Device authorization endpoint string. | no
|
||||
token_uri \| ipaidptokenendpoint | Token endpoint string. | no
|
||||
userinfo_uri \| ipaidpuserinfoendpoint | User information endpoint string. | no
|
||||
keys_uri \| ipaidpkeysendpoint | JWKS endpoint string. | no
|
||||
issuer_url \| ipaidpissuerurl | The Identity Provider OIDC URL string. | no
|
||||
client_id \| ipaidpclientid | OAuth 2.0 client identifier string. | no
|
||||
secret \| ipaidpclientsecret | OAuth 2.0 client secret string. | no
|
||||
scope \| ipaidpscope | OAuth 2.0 scope string. Multiple scopes separated by space. | no
|
||||
idp_user_id \| ipaidpsub | Attribute string for user identity in OAuth 2.0 userinfo. | no
|
||||
provider \| ipaidpprovider | Pre-defined template string. This provides the provider defaults, which can be overridden with the other IdP options. Choices: ["google","github","microsoft","okta","keycloak"] | no
|
||||
organization \| ipaidporg | Organization ID string or Realm name for IdP provider templates. | no
|
||||
base_url \| ipaidpbaseurl | Base URL string for IdP provider templates. | no
|
||||
rename \| new_name | New name for the Identity Provider server object. Only with `state: renamed`. | no
|
||||
delete_continue \| continue | Continuous mode. Don't stop on errors. Valid only if `state` is `absent`. | no
|
||||
`state` | The state to ensure. It can be one of `present`, `absent`, `renamed`, default: `present`. | no
|
||||
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Thomas Woerner
|
||||
@@ -37,7 +37,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
153
README-idview.md
Normal file
153
README-idview.md
Normal file
@@ -0,0 +1,153 @@
|
||||
Idview module
|
||||
============
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The idview module allows to ensure presence and absence of idviews and idview host members.
|
||||
|
||||
Use Cases
|
||||
---------
|
||||
|
||||
With ID views it is possible to override user or group attributes for users stored in the LDAP server. For example the login name, home directory, certificate for authentication or SSH keys. An ID view is client-side and specifies new values for user or group attributes and also the client host or hosts on which the values apply.
|
||||
|
||||
The ID view and the applied hosts are managed with idview, the user attributes are managed with idoverrideuser and the group attributes with idoverridegroup.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Idview management
|
||||
|
||||
|
||||
Supported FreeIPA Versions
|
||||
--------------------------
|
||||
|
||||
FreeIPA versions 4.4.0 and up are supported by the ipaidview module.
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Example inventory file
|
||||
|
||||
```ini
|
||||
[ipaserver]
|
||||
ipaserver.test.local
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure idview "test_idview" is present:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage IPA idview.
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- ipaidview:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: test_idview
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure idview "test_idview" member host "testhost.example.com" is present:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage IPA idview host member.
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- ipaidview:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: test_idview
|
||||
host: testhost.example.com
|
||||
action: member
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure idview "test_idview" member host "testhost.example.com" is absent:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage IPA idview host member.
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- ipaidview:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: test_idview
|
||||
host: testhost.example.com
|
||||
action: member
|
||||
state: absent
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure idview "test_idview" is present with domain_resolution_order for "ad.example.com:ipa.example.com":
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage IPA idview host member.
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- ipaidview:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: test_idview
|
||||
domain_resolution_order: "ad.example.com:ipa.example.com"
|
||||
```
|
||||
|
||||
|
||||
Example playbook to make sure idview "test_idview" is absent:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to manage IPA idview.
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- ipaidview:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: test_idview
|
||||
state: absent
|
||||
```
|
||||
|
||||
|
||||
Variables
|
||||
---------
|
||||
|
||||
Variable | Description | Required
|
||||
-------- | ----------- | --------
|
||||
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
|
||||
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
|
||||
`ipaapi_context` | The context in which the module will execute. Executing in a server context is preferred. If not provided context will be determined by the execution environment. Valid values are `server` and `client`. | no
|
||||
`ipaapi_ldap_cache` | Use LDAP cache for IPA connection. The bool setting defaults to true. (bool) | no
|
||||
`name` \| `cn` | The list of idview name strings. | yes
|
||||
`description` \| `desc` | The description string of the idview. | no
|
||||
`domain_resolution_order` \| `ipadomainresolutionorder` | Colon-separated list of domains used for short name qualification. | no
|
||||
`host` \| `hosts` | List of hosts to apply the ID View to. A host can only be applied to a single idview at any time. Applying a host that is already applied to a different idview will change the idview the host is applied to to the new one. | no
|
||||
`rename` \| `new_name` | Rename the ID view object to the new name string. Only usable with `state: renamed`. | no
|
||||
`delete_continue` \| `continue` | Continuous mode. Don't stop on errors. Valid only if `state` is `absent`. | no
|
||||
`action` | Work on idview or member level. It can be on of `member` or `idview` and defaults to `idview`. | no
|
||||
`state` | The state to ensure. It can be one of `present`, `absent` and `renamed`, default: `present`. | no
|
||||
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Thomas Woerner
|
||||
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.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
@@ -128,20 +128,20 @@ Variable | Description | Required
|
||||
`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` \| `cn` | The list of pwpolicy name strings. If name is not given, `global_policy` will be used automatically. | no
|
||||
`maxlife` \| `krbmaxpwdlife` | Maximum password lifetime in days. (int) | no
|
||||
`minlife` \| `krbminpwdlife` | Minimum password lifetime in hours. (int) | no
|
||||
`history` \| `krbpwdhistorylength` | Password history size. (int) | no
|
||||
`minclasses` \| `krbpwdmindiffchars` | Minimum number of character classes. (int) | no
|
||||
`minlength` \| `krbpwdminlength` | Minimum length of password. (int) | no
|
||||
`priority` \| `cospriority` | Priority of the policy, higher number means lower priority. (int) | no
|
||||
`maxfail` \| `krbpwdmaxfailure` | Consecutive failures before lockout. (int) | no
|
||||
`failinterval` \| `krbpwdfailurecountinterval` | Period after which failure count will be reset in seconds. (int) | no
|
||||
`lockouttime` \| `krbpwdlockoutduration` | Period for which lockout is enforced in seconds. (int) | no
|
||||
`maxrepeat` \| `ipapwdmaxrepeat` | Maximum number of same consecutive characters. Requires IPA 4.9+ (int) | no
|
||||
`maxsequence` \| `ipapwdmaxsequence` | The maximum length of monotonic character sequences (abcd). Requires IPA 4.9+ (int) | no
|
||||
`dictcheck` \| `ipapwdictcheck` | Check if the password is a dictionary word. Requires IPA 4.9+ (int) | no
|
||||
`usercheck` \| `ipapwdusercheck` | Check if the password contains the username. Requires IPA 4.9+ (int) | no
|
||||
`gracelimit` \| `passwordgracelimit` | Number of LDAP authentications allowed after expiration. Requires IPA 4.9.10 (int) | no
|
||||
`maxlife` \| `krbmaxpwdlife` | Maximum password lifetime in days. (int or "") | no
|
||||
`minlife` \| `krbminpwdlife` | Minimum password lifetime in hours. (int or "") | no
|
||||
`history` \| `krbpwdhistorylength` | Password history size. (int or "") | no
|
||||
`minclasses` \| `krbpwdmindiffchars` | Minimum number of character classes. (int or "") | no
|
||||
`minlength` \| `krbpwdminlength` | Minimum length of password. (int or "") | no
|
||||
`priority` \| `cospriority` | Priority of the policy, higher number means lower priority. (int or "") | no
|
||||
`maxfail` \| `krbpwdmaxfailure` | Consecutive failures before lockout. (int or "") | no
|
||||
`failinterval` \| `krbpwdfailurecountinterval` | Period after which failure count will be reset in seconds. (int or "") | no
|
||||
`lockouttime` \| `krbpwdlockoutduration` | Period for which lockout is enforced in seconds. (int or "") | no
|
||||
`maxrepeat` \| `ipapwdmaxrepeat` | Maximum number of same consecutive characters. Requires IPA 4.9+ (int or "") | no
|
||||
`maxsequence` \| `ipapwdmaxsequence` | The maximum length of monotonic character sequences (abcd). Requires IPA 4.9+ (int or "") | no
|
||||
`dictcheck` \| `ipapwdictcheck` | Check if the password is a dictionary word. Requires IPA 4.9+. (bool or "") | no
|
||||
`usercheck` \| `ipapwdusercheck` | Check if the password contains the username. Requires IPA 4.9+. (bool or "") | no
|
||||
`gracelimit` \| `passwordgracelimit` | Number of LDAP authentications allowed after expiration. Requires IPA 4.9.10 (int or "") | no
|
||||
`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | yes
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -23,7 +23,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
@@ -249,14 +249,14 @@ Variable | Description | Required
|
||||
`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` \| `cn` | The list of server name strings. | yes
|
||||
`location` \| `ipalocation_location` | The server location string. Only in state: present. "" for location reset. | no
|
||||
`service_weight` \| `ipaserviceweight` | Weight for server services. Type Values 0 to 65535, -1 for weight reset. Only in state: present. (int) | no
|
||||
`hidden` | Set hidden state of a server. Only in state: present. (bool) | no
|
||||
`no_members` | Suppress processing of membership attributes. Only in state: present. (bool) | no
|
||||
`delete_continue` \| `continue` | Continuous mode: Don't stop on errors. Only in state: absent. (bool) | no
|
||||
`ignore_last_of_role` | Skip a check whether the last CA master or DNS server is removed. Only in state: absent. (bool) | no
|
||||
`ignore_topology_disconnect` | Ignore topology connectivity problems after removal. Only in state: absent. (bool) | no
|
||||
`force` | Force server removal even if it does not exist. Will always result in changed. Only in state: absent. (bool) | no
|
||||
`location` \| `ipalocation_location` | The server DNS location. Only available with 'state: present'. Use "" for location reset. | no
|
||||
`service_weight` \| `ipaserviceweight` | Weight for server services. Type Values 0 to 65535, -1 for weight reset. Only available with 'state: present'. (int) | no
|
||||
`hidden` | Set hidden state of a server. Only available with 'state: present'. (bool) | no
|
||||
`no_members` | Suppress processing of membership attributes. Only avialable with 'state: present'. (bool) | no
|
||||
`delete_continue` \| `continue` | Continuous mode: Don't stop on errors. Only available with 'state: absent'. (bool) | no
|
||||
`ignore_last_of_role` | Skip a check whether the last CA master or DNS server is removed. Only available with 'state: absent'. (bool) | no
|
||||
`ignore_topology_disconnect` | Ignore topology connectivity problems after removal. Only available with 'state: absent'. (bool) | no
|
||||
`force` | Force server removal even if it does not exist. Will always result in changed. Only available with 'state: absent'. (bool) | no
|
||||
`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. `present` is only working with existing servers. | no
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* 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,10 +350,18 @@ 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` or `hardened`. Use empty string to reset auth_ind to the initial value. | no
|
||||
`auth_ind` \| `krbprincipalauthind` | Defines an allow list for Authentication Indicators. It can be any of `otp`, `radius`, `pkinit`, `hardened`, `idp` or `""`. An additional check ensures that only types can be used that are supported by the IPA version. Use empty string to reset auth_ind to the initial value. | no
|
||||
`requires_pre_auth` \| `ipakrbrequirespreauth` | Pre-authentication is required for the service. Default to true. (bool) | no
|
||||
`ok_as_delegate` \| `ipakrbokasdelegate` | Client credentials may be delegated to the service. Default to false. (bool) | no
|
||||
`ok_to_auth_as_delegate` \| `ipakrboktoauthasdelegate` | The service is allowed to authenticate on behalf of a client. Default to false. (bool) | no
|
||||
@@ -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.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -24,7 +24,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -24,7 +24,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -24,7 +24,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -22,7 +22,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* 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.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
@@ -21,7 +21,7 @@ Requirements
|
||||
|
||||
**Controller**
|
||||
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
@@ -58,6 +58,7 @@ Example playbook to ensure a user is present:
|
||||
last: Acme
|
||||
uid: 10001
|
||||
gid: 100
|
||||
gecos: "The Pinky"
|
||||
phone: "+555123457"
|
||||
email: pinky@acme.com
|
||||
passwordexpiration: "2023-01-19 23:59:59"
|
||||
@@ -278,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
|
||||
@@ -297,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:
|
||||
|
||||
@@ -352,6 +368,33 @@ Example playbook to ensure users are absent:
|
||||
state: absent
|
||||
```
|
||||
|
||||
When using FreeIPA 4.8.0+, SMB logon script, profile, home directory and home drive can be set for users.
|
||||
|
||||
In the example playbook to set SMB attributes note that `smb_profile_path` and `smb_home_dir` use paths in UNC format, which includes backslashes ('\\`). If the paths are quoted, the backslash needs to be escaped becoming "\\", so the path `\\server\dir` becomes `"\\\\server\\dir"`. If the paths are unquoted the slashes do not have to be escaped.
|
||||
|
||||
The YAML specification states that a colon (':') is a key separator and a dash ('-') is an item marker, only with a space after them, so using both unquoted as part of a path should not be a problem. If a space is needed after a colon or a dash, then a quoted string must be used as in `"user - home"`. For the `smb_home_drive` attribute is is recomended that a quoted string is used, to improve readability.
|
||||
|
||||
Example playbook to set SMB attributes:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Plabook to handle users
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure user 'smbuser' is present with smb attributes
|
||||
ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: smbuser
|
||||
first: SMB
|
||||
last: User
|
||||
smb_logon_script: N:\logonscripts\startup
|
||||
smb_profile_path: \\server\profiles\some_profile
|
||||
smb_home_dir: \\users\home\smbuser
|
||||
smb_home_drive: "U:"
|
||||
```
|
||||
|
||||
|
||||
Variables
|
||||
=========
|
||||
@@ -373,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
|
||||
|
||||
|
||||
|
||||
@@ -393,8 +436,10 @@ Variable | Description | Required
|
||||
`passwordexpiration` \| `krbpasswordexpiration` | The kerberos password expiration date. Possible formats: `YYYYMMddHHmmssZ`, `YYYY-MM-ddTHH:mm:ssZ`, `YYYY-MM-ddTHH:mmZ`, `YYYY-MM-ddZ`, `YYYY-MM-dd HH:mm:ssZ` or `YYYY-MM-dd HH:mmZ`. The trailing 'Z' can be skipped. Only usable with IPA versions 4.7 and up. | no
|
||||
`password` | The user password string. | no
|
||||
`random` | Generate a random user password | no
|
||||
`uid` \| `uidnumber` | The UID integer. | no
|
||||
`gid` \| `gidnumber` | The GID integer. | no
|
||||
`uid` \| `uidnumber` | User ID Number (system will assign one if not provided). | no
|
||||
`gid` \| `gidnumber` | Group ID Number. | no
|
||||
`gecos` | GECOS | no
|
||||
`street` | Street address | no
|
||||
`city` | City | no
|
||||
`userstate` \| `st` | State/Province | no
|
||||
`postalcode` \| `zip` | Postalcode/ZIP | no
|
||||
@@ -407,7 +452,7 @@ Variable | Description | Required
|
||||
`manager` | List of manager user names. | no
|
||||
`carlicense` | List of car licenses. | no
|
||||
`sshpubkey` \| `ipasshpubkey` | List of SSH public keys. | no
|
||||
`userauthtype` | List of supported user authentication types. Choices: `password`, `radius`, `otp` and ``. Use empty string to reset userauthtype to the initial value. | no
|
||||
`userauthtype` \| `ipauserauthtype` | List of supported user authentication types. Choices: `password`, `radius`, `otp`, `pkinit`, `hardened`, `idp` and `""`. An additional check ensures that only types can be used that are supported by the IPA version. Use empty string to reset userauthtype to the initial value. | no
|
||||
`userclass` | User category. (semantics placed on this attribute are for local interpretation). | no
|
||||
`radius` | RADIUS proxy configuration | no
|
||||
`radiususer` | RADIUS proxy username | no
|
||||
@@ -415,6 +460,8 @@ Variable | Description | Required
|
||||
`employeenumber` | Employee Number | no
|
||||
`employeetype` | Employee Type | no
|
||||
`preferredlanguage` | Preferred Language | no
|
||||
`idp` \| `ipaidpconfiglink` | External IdP configuration | no
|
||||
`idp_user_id` \| `ipaidpsub` | A string that identifies the user at external IdP | no
|
||||
`certificate` | List of base-64 encoded user certificates. | no
|
||||
`certmapdata` | List of certificate mappings. Either `data` or `certificate` or `issuer` together with `subject` need to be specified. Only usable with IPA versions 4.5 and up. <br>Options: | no
|
||||
| `certificate` - Base-64 encoded user certificate, not usable with other certmapdata options. | no
|
||||
@@ -422,10 +469,14 @@ Variable | Description | Required
|
||||
| `subject` - Subject of the certificate, only usable together with `issuer` option. | no
|
||||
| `data` - Certmap data, not usable with other certmapdata options. | no
|
||||
`noprivate` | Do not create user private group. (bool) | no
|
||||
`smb_logon_script` \| `ipantlogonscript` | SMB logon script path. Requires FreeIPA version 4.8.0+. | no
|
||||
`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
|
||||
=============
|
||||
|
||||
@@ -434,11 +485,12 @@ There are only return values if one or more random passwords have been generated
|
||||
Variable | Description | Returned When
|
||||
-------- | ----------- | -------------
|
||||
`user` | User dict with random password. (dict) <br>Options: | If random is yes and user did not exist or update_password is yes
|
||||
| `randompassword` - The generated random password | If only one user is handled by the module
|
||||
| `name` - The user name of the user that got a new random password. (dict) <br> Options: <br> `randompassword` - The generated random password | If several users are handled by the module
|
||||
| `randompassword` - The generated random password | If only one user is handled by the module without using the `users` parameter.
|
||||
| `name` - The user name of the user that got a new random password. (dict) <br> Options: <br> `randompassword` - The generated random password | If several users are handled by the module with the `users` parameter.
|
||||
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Thomas Woerner
|
||||
- Thomas Woerner
|
||||
- Rafael Jeffman
|
||||
|
||||
@@ -24,7 +24,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
35
README.md
35
README.md
@@ -13,10 +13,12 @@ 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
|
||||
* Modules for automount map management
|
||||
* Modules for certificate management
|
||||
* Modules for config management
|
||||
* Modules for delegation management
|
||||
* Modules for dns config management
|
||||
@@ -29,7 +31,11 @@ Features
|
||||
* Modules for hbacsvcgroup management
|
||||
* Modules for host management
|
||||
* Modules for hostgroup management
|
||||
* Modules for idoverridegroup management
|
||||
* Modules for idoverrideuser management
|
||||
* Modules for idp management
|
||||
* Modules for idrange management
|
||||
* Modules for idview management
|
||||
* Modules for location management
|
||||
* Modules for netgroup management
|
||||
* Modules for permission management
|
||||
@@ -68,7 +74,7 @@ Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+ (ansible-freeipa is an Ansible Collection)
|
||||
* Ansible version: 2.15+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
@@ -103,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:
|
||||
@@ -128,18 +135,8 @@ This command will get the whole collection from galaxy:
|
||||
ansible-galaxy collection install freeipa.ansible_freeipa
|
||||
```
|
||||
|
||||
Installing collections using the ansible-galaxy command is only supported with ansible 2.9+.
|
||||
|
||||
The mazer tool can be used for to install the collection for ansible 2.8:
|
||||
|
||||
```bash
|
||||
mazer install freeipa.ansible_freeipa
|
||||
```
|
||||
|
||||
Ansible galaxy does not support the use of dash ('-') in a name and is automatically replacing this with an underscore ('\_'). Therefore the name is `ansible_freeipa`. The ansible_freeipa collection will be placed in the directory `~/.ansible/collections/ansible_collections/freeipa/ansible_freeipa` where it will be automatically be found for this user.
|
||||
|
||||
The needed adaptions of collection prefixes for `modules` and `module_utils` will be done with ansible-freeipa release `0.1.6` for galaxy.
|
||||
|
||||
|
||||
Ansible inventory file
|
||||
----------------------
|
||||
@@ -436,6 +433,7 @@ Modules in plugin/modules
|
||||
* [ipaautomountkey](README-automountkey.md)
|
||||
* [ipaautomountlocation](README-automountlocation.md)
|
||||
* [ipaautomountmap](README-automountmap.md)
|
||||
* [ipacert](README-cert.md)
|
||||
* [ipaconfig](README-config.md)
|
||||
* [ipadelegation](README-delegation.md)
|
||||
* [ipadnsconfig](README-dnsconfig.md)
|
||||
@@ -448,7 +446,11 @@ Modules in plugin/modules
|
||||
* [ipahbacsvcgroup](README-hbacsvcgroup.md)
|
||||
* [ipahost](README-host.md)
|
||||
* [ipahostgroup](README-hostgroup.md)
|
||||
* [idoverridegroup](README-idoverridegroup.md)
|
||||
* [idoverrideuser](README-idoverrideuser.md)
|
||||
* [idp](README-idp.md)
|
||||
* [idrange](README-idrange.md)
|
||||
* [idview](README-idview.md)
|
||||
* [ipalocation](README-location.md)
|
||||
* [ipanetgroup](README-netgroup.md)
|
||||
* [ipapermission](README-permission.md)
|
||||
@@ -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)
|
||||
|
||||
@@ -13,8 +13,8 @@ homepage: "https://github.com/freeipa/ansible-freeipa"
|
||||
issues: "https://github.com/freeipa/ansible-freeipa/issues"
|
||||
|
||||
readme: "README.md"
|
||||
license: "GPL-3.0-or-later"
|
||||
|
||||
license:
|
||||
- "GPL-3.0-or-later"
|
||||
tags:
|
||||
- "linux"
|
||||
- "system"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
requires_ansible: ">=2.9"
|
||||
requires_ansible: ">=2.15.0"
|
||||
|
||||
14
playbooks/automount/automount-map-indirect-map.yml
Normal file
14
playbooks/automount/automount-map-indirect-map.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
- name: Managed automount maps
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: Playbook to add an indirect automount map
|
||||
ipaautomountmap:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: auto.indirect
|
||||
location: DMZ
|
||||
parent: auto.DMZ
|
||||
mount: dmz_indirect
|
||||
15
playbooks/cert/cert-hold.yml
Normal file
15
playbooks/cert/cert-hold.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Certificate manage example
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
gather_facts: false
|
||||
module_defaults:
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: client
|
||||
|
||||
tasks:
|
||||
- name: Temporarily hold a certificate
|
||||
ipacert:
|
||||
serial_number: 12345
|
||||
state: held
|
||||
15
playbooks/cert/cert-release.yml
Normal file
15
playbooks/cert/cert-release.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Certificate manage example
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
gather_facts: false
|
||||
module_defaults:
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: client
|
||||
|
||||
tasks:
|
||||
- name: Release a certificate hold
|
||||
ipacert:
|
||||
serial_number: 12345
|
||||
state: released
|
||||
26
playbooks/cert/cert-request-host.yml
Normal file
26
playbooks/cert/cert-request-host.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
- name: Certificate manage example
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
gather_facts: false
|
||||
module_defaults:
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: client
|
||||
|
||||
tasks:
|
||||
- name: Request a certificate for a host
|
||||
ipacert:
|
||||
csr: |
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIBWjCBxAIBADAbMRkwFwYDVQQDDBBob3N0LmV4YW1wbGUuY29tMIGfMA0GCSqG
|
||||
SIb3DQEBAQUAA4GNADCBiQKBgQCzR3Vd4Cwl0uVgwB3+wxz+4JldFk3x526bPeuK
|
||||
g8EEc+rEHILzJWeXC8ywCYPOgK9n7hrdMfVQiIx3yHYrY+0IYuLehWow4o1iJEf5
|
||||
urPNAP9K9C4Y7MMXzzoQmoWR3IFQQpOYwvWOtiZfvrhmtflnYEGLE2tgz53gOQHD
|
||||
NnbCCwIDAQABoAAwDQYJKoZIhvcNAQELBQADgYEAgF+6YC39WhnvmFgNz7pjAh5E
|
||||
2ea3CgG+zrzAyiSBGG6WpXEjqMRnAQxciQNGxQacxjwWrscZidZzqg8URJPugewq
|
||||
tslYB1+RkZn+9UWtfnWvz89+xnOgco7JlytnbH10Nfxt5fXXx13rY0tl54jBtk2W
|
||||
422eYZ12wb4gjNcQy3A=
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
principal: host/host.example.com
|
||||
state: requested
|
||||
23
playbooks/cert/cert-request-service.yml
Normal file
23
playbooks/cert/cert-request-service.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
- name: Certificate manage example
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
gather_facts: false
|
||||
module_defaults:
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: client
|
||||
|
||||
tasks:
|
||||
- name: Request a certificate for a service
|
||||
ipacert:
|
||||
csr: |
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIGYMEwCAQAwGTEXMBUGA1UEAwwOZnJlZWlwYSBydWxlcyEwKjAFBgMrZXADIQBs
|
||||
HlqIr4b/XNK+K8QLJKIzfvuNK0buBhLz3LAzY7QDEqAAMAUGAytlcANBAF4oSCbA
|
||||
5aIPukCidnZJdr491G4LBE+URecYXsPknwYb+V+ONnf5ycZHyaFv+jkUBFGFeDgU
|
||||
SYaXm/gF8cDYjQI=
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
principal: HTTP/www.example.com
|
||||
add: true
|
||||
state: requested
|
||||
27
playbooks/cert/cert-request-user.yml
Normal file
27
playbooks/cert/cert-request-user.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
- name: Certificate manage example
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
gather_facts: false
|
||||
module_defaults:
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: client
|
||||
|
||||
tasks:
|
||||
- name: Request a certificate for a user with a specific profile
|
||||
ipacert:
|
||||
csr: |
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIBejCB5AIBADAQMQ4wDAYDVQQDDAVwaW5reTCBnzANBgkqhkiG9w0BAQEFAAOB
|
||||
jQAwgYkCgYEA7uChccy1Is1FTM0SF23WPYW472E3ozeLh2kzhKR9Ni6FLmeEGgu7
|
||||
/hicR1VwvXHYkNwI1tpW9LqxRVvgr6vheqHySljrBcoRfshfYvKejp03l2327Bfq
|
||||
BNxXqLcHylNEyg8SH0u63bWyxtgoDBfdZwdGAhYuJ+g4ev79J5eYoB0CAwEAAaAr
|
||||
MCkGCSqGSIb3DQEJDjEcMBowGAYHKoZIzlYIAQQNDAtoZWxsbyB3b3JsZDANBgkq
|
||||
hkiG9w0BAQsFAAOBgQADCi5BHDv1mrBFDWqYytFpQ1mrvr/mdax3AYXxNL2UEV8j
|
||||
AqZAFTEnJXL/u1eVQtI1yotqxakyUBN4XZBP2CBgJRO93Mtry8cgvU1sPdU8Mavx
|
||||
5gSnlP74Hio2ziscWWydlxpYxFx0gkKvu+0nyIpz954SVYwQ2wwk5FRqZnxI5w==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
principal: pinky
|
||||
profile: IECUserRoles
|
||||
state: requested
|
||||
16
playbooks/cert/cert-retrieve.yml
Normal file
16
playbooks/cert/cert-retrieve.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
- name: Certificate manage example
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
gather_facts: false
|
||||
module_defaults:
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: client
|
||||
|
||||
tasks:
|
||||
- name: Retrieve a certificate
|
||||
ipacert:
|
||||
serial_number: 12345
|
||||
state: retrieved
|
||||
register: cert_retrieved
|
||||
18
playbooks/cert/cert-revoke.yml
Normal file
18
playbooks/cert/cert-revoke.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
- name: Certificate manage example
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
gather_facts: false
|
||||
module_defaults:
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: client
|
||||
|
||||
tasks:
|
||||
- name: Permanently revoke a certificate issued by a lightweight sub-CA
|
||||
ipacert:
|
||||
serial_number: 12345
|
||||
ca: vpn-ca
|
||||
# reason: keyCompromise (1)
|
||||
reason: 1
|
||||
state: revoked
|
||||
32
playbooks/group/add-groups.yml
Normal file
32
playbooks/group/add-groups.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
- name: Playbook to handle multiple groups
|
||||
hosts: ipaserver
|
||||
|
||||
tasks:
|
||||
- name: Create multiple groups ops, sysops
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: ops
|
||||
gidnumber: 1234
|
||||
- name: sysops
|
||||
|
||||
- name: Add user and group members to groups sysops and appops
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: sysops
|
||||
user:
|
||||
- user1
|
||||
- name: appops
|
||||
group:
|
||||
- group2
|
||||
|
||||
- name: Create multiple non-POSIX and external groups
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: nongroup
|
||||
nonposix: true
|
||||
- name: extgroup
|
||||
external: true
|
||||
13
playbooks/idoverridegroup/idoverridegroup-absent.yml
Normal file
13
playbooks/idoverridegroup/idoverridegroup-absent.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
- name: Playbook to manage idoverridegroup
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
|
||||
tasks:
|
||||
- name: Ensure idoverridegroup test_group is absent in idview test_idview.
|
||||
ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
continue: true
|
||||
state: absent
|
||||
11
playbooks/idoverridegroup/idoverridegroup-present.yml
Normal file
11
playbooks/idoverridegroup/idoverridegroup-present.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Playbook to manage idoverridegroup
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
|
||||
tasks:
|
||||
- name: Ensure idoverridegroup test_group is present in idview test_idview.
|
||||
ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
13
playbooks/idoverrideuser/idoverrideuser-absent.yml
Normal file
13
playbooks/idoverrideuser/idoverrideuser-absent.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is absent in idview test_idview
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
continue: true
|
||||
state: absent
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user certificate member is absent in idview test_idview
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
certificate:
|
||||
- "{{ lookup('file', 'cert1.b64', rstrip=False) }}"
|
||||
action: member
|
||||
state: absent
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user certificate member is present in idview test_idview
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
certificate:
|
||||
- "{{ lookup('file', 'cert1.b64', rstrip=False) }}"
|
||||
action: member
|
||||
11
playbooks/idoverrideuser/idoverrideuser-present.yml
Normal file
11
playbooks/idoverrideuser/idoverrideuser-present.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Playbook to manage idoverrideuser
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure test user test_user is present in idview test_idview.
|
||||
ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
11
playbooks/idp/idp-absent.yml
Normal file
11
playbooks/idp/idp-absent.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Idp absent example
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
|
||||
tasks:
|
||||
- name: Ensure github idp my-github-idp is absent
|
||||
ipaidp:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: my-github-idp
|
||||
state: absent
|
||||
12
playbooks/idp/idp-present.yml
Normal file
12
playbooks/idp/idp-present.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Idp present example
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
|
||||
tasks:
|
||||
- name: Ensure github idp my-github-idp is present
|
||||
ipaidp:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: my-github-idp
|
||||
provider: github
|
||||
client_id: my-github-client-id
|
||||
11
playbooks/idview/idview-absent.yml
Normal file
11
playbooks/idview/idview-absent.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Idview absent example
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
|
||||
tasks:
|
||||
- name: Ensure idview test_idview is absent
|
||||
ipaidview:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: test_idview
|
||||
state: absent
|
||||
12
playbooks/idview/idview-host-applied.yml
Normal file
12
playbooks/idview/idview-host-applied.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Idview host member applied example
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
|
||||
tasks:
|
||||
- name: Ensure host testhost.example.com is applied to idview test_idview
|
||||
ipaidview:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: test_idview
|
||||
host: testhost.example.com
|
||||
action: member
|
||||
13
playbooks/idview/idview-host-unapplied.yml
Normal file
13
playbooks/idview/idview-host-unapplied.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
- name: Idview host member unapplied example
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
|
||||
tasks:
|
||||
- name: Ensure host testhost.example.com is not applied to idview test_idview
|
||||
ipaidview:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: test_idview
|
||||
host: testhost.example.com
|
||||
action: member
|
||||
state: absent
|
||||
10
playbooks/idview/idview-present.yml
Normal file
10
playbooks/idview/idview-present.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: Idview present example
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
|
||||
tasks:
|
||||
- name: Ensure idview test_idview is present
|
||||
ipaidview:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: test_idview
|
||||
@@ -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
|
||||
12
playbooks/user/add-user-external-idp.yml
Normal file
12
playbooks/user/add-user-external-idp.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Playbook to handle users
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Create user associated with an external IdP
|
||||
ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: idpuser
|
||||
idp: keycloak
|
||||
idp_user_id: idpuser@exemple.com
|
||||
17
playbooks/user/smb-attributes.yml
Normal file
17
playbooks/user/smb-attributes.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
- name: Plabook to handle users
|
||||
hosts: ipaserver
|
||||
become: false
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: Ensure user 'smbuser' is present with smb attributes
|
||||
ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: smbuser
|
||||
first: SMB
|
||||
last: User
|
||||
smb_logon_script: N:\logonscripts\startup
|
||||
smb_profile_path: \\server\profiles\some_profile
|
||||
smb_home_dir: \\users\home\smbuser
|
||||
smb_home_drive: "U:"
|
||||
@@ -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,11 +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",
|
||||
"load_pem_x509_certificate", "DNSName", "getargspec"]
|
||||
"DNSName", "getargspec", "certificate_loader",
|
||||
"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.
|
||||
@@ -41,6 +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
|
||||
@@ -48,6 +64,7 @@ from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.common.text.converters import jsonify
|
||||
from ansible.module_utils import six
|
||||
from ansible.module_utils.common._collections_compat import Mapping
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
|
||||
# Import getargspec from inspect or provide own getargspec for
|
||||
# Python 2 compatibility with Python 3.11+.
|
||||
@@ -83,10 +100,15 @@ 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
|
||||
from ipapython.version import VERSION
|
||||
from ipaplatform.paths import paths
|
||||
@@ -106,6 +128,7 @@ try:
|
||||
except ImportError:
|
||||
from ipalib.x509 import load_certificate
|
||||
certificate_loader = load_certificate
|
||||
from ipalib.x509 import write_certificate_list
|
||||
|
||||
# Try to import is_ipa_configured or use a fallback implementation.
|
||||
try:
|
||||
@@ -142,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)
|
||||
|
||||
@@ -222,7 +252,7 @@ def temp_kdestroy(ccache_dir, ccache_name):
|
||||
"""Destroy temporary ticket and remove temporary ccache."""
|
||||
if ccache_name is not None:
|
||||
run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False)
|
||||
del os.environ['KRB5CCNAME']
|
||||
os.environ.pop('KRB5CCNAME', None)
|
||||
if ccache_dir is not None:
|
||||
shutil.rmtree(ccache_dir, ignore_errors=True)
|
||||
|
||||
@@ -459,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)
|
||||
@@ -484,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)):
|
||||
@@ -493,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
|
||||
|
||||
@@ -571,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)
|
||||
@@ -581,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-----"):
|
||||
@@ -747,8 +849,8 @@ def exit_raw_json(module, **kwargs):
|
||||
contains sensible data, it will be appear in the logs.
|
||||
"""
|
||||
module.do_cleanup_files()
|
||||
print(jsonify(kwargs))
|
||||
sys.exit(0)
|
||||
print(jsonify(kwargs)) # pylint: disable=W0012,ansible-bad-function
|
||||
sys.exit(0) # pylint: disable=W0012,ansible-bad-function
|
||||
|
||||
|
||||
def __get_domain_validator():
|
||||
@@ -1040,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.
|
||||
|
||||
@@ -1048,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.
|
||||
|
||||
@@ -1062,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):
|
||||
"""
|
||||
@@ -1166,6 +1291,45 @@ class IPAAnsibleModule(AnsibleModule):
|
||||
"""
|
||||
return api_check_param(command, name)
|
||||
|
||||
def ipa_command_invalid_param_choices(self, command, name, value):
|
||||
"""
|
||||
Return invalid parameter choices for IPA command.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
command: string
|
||||
The IPA API command to test.
|
||||
name: string
|
||||
The parameter name to check.
|
||||
value: string
|
||||
The parameter value to verify.
|
||||
|
||||
"""
|
||||
if command not in api.Command:
|
||||
self.fail_json(msg="The command '%s' does not exist." % command)
|
||||
if name not in api.Command[command].params:
|
||||
self.fail_json(msg="The command '%s' does not have a parameter "
|
||||
"named '%s'." % (command, name))
|
||||
if not hasattr(api.Command[command].params[name], "cli_metavar"):
|
||||
self.fail_json(msg="The parameter '%s' of the command '%s' does "
|
||||
"not have choices." % (name, command))
|
||||
# For IPA 4.6 (RHEL-7):
|
||||
# - krbprincipalauthind in host_add does not have choices defined
|
||||
# - krbprincipalauthind in service_add does not have choices defined
|
||||
#
|
||||
# api.Command[command].params[name].cli_metavar returns "STR" and
|
||||
# ast.literal_eval failes with a ValueError "malformed string".
|
||||
#
|
||||
# There is no way to verify that the given values are valid or not in
|
||||
# this case. The check is done later on while applying the change
|
||||
# with host_add, host_mod, service_add and service_mod.
|
||||
try:
|
||||
_choices = ast.literal_eval(
|
||||
api.Command[command].params[name].cli_metavar)
|
||||
except ValueError:
|
||||
return None
|
||||
return (set(value or []) - set([""])) - set(_choices)
|
||||
|
||||
@staticmethod
|
||||
def ipa_check_version(oper, requested_version):
|
||||
"""
|
||||
@@ -1195,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.
|
||||
|
||||
@@ -1212,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
|
||||
@@ -1281,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)
|
||||
|
||||
@@ -121,8 +121,7 @@ class AutomountKey(IPAAnsibleModule):
|
||||
resp = self.ipa_command("automountkey_show", location, args)
|
||||
except ipalib_errors.NotFound:
|
||||
return None
|
||||
else:
|
||||
return resp.get("result")
|
||||
return resp.get("result")
|
||||
|
||||
def check_ipa_params(self):
|
||||
invalid = []
|
||||
|
||||
@@ -92,8 +92,7 @@ class AutomountLocation(IPAAnsibleModule):
|
||||
)
|
||||
except ipalib_errors.NotFound:
|
||||
return None
|
||||
else:
|
||||
return response.get("result", None)
|
||||
return response.get("result", None)
|
||||
|
||||
def check_ipa_params(self):
|
||||
if len(self.params_get("name")) == 0:
|
||||
|
||||
@@ -37,6 +37,7 @@ module: ipaautomountmap
|
||||
author:
|
||||
- Chris Procter (@chr15p)
|
||||
- Thomas Woerner (@t-woerner)
|
||||
- Rafael Jeffman (@rjeffman)
|
||||
short_description: Manage FreeIPA autommount map
|
||||
description:
|
||||
- Add, delete, and modify an IPA automount map
|
||||
@@ -59,6 +60,16 @@ options:
|
||||
type: str
|
||||
aliases: ["description"]
|
||||
required: false
|
||||
parentmap:
|
||||
description: |
|
||||
Parent map of the indirect map. Can only be used when creating
|
||||
new maps.
|
||||
type: str
|
||||
required: false
|
||||
mount:
|
||||
description: Indirect map mount point, relative to parent map.
|
||||
type: str
|
||||
required: false
|
||||
state:
|
||||
description: State to ensure
|
||||
type: str
|
||||
@@ -75,6 +86,14 @@ EXAMPLES = '''
|
||||
location: DMZ
|
||||
desc: "this is a map for servers in the DMZ"
|
||||
|
||||
- name: ensure indirect map exists
|
||||
ipaautomountmap:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: auto.INDIRECT
|
||||
location: DMZ
|
||||
parentmap: auto.DMZ
|
||||
mount: indirect
|
||||
|
||||
- name: remove a map named auto.DMZ in location DMZ if it exists
|
||||
ipaautomountmap:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
@@ -107,8 +126,36 @@ class AutomountMap(IPAAnsibleModule):
|
||||
)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
return None
|
||||
else:
|
||||
return response["result"]
|
||||
return response["result"]
|
||||
|
||||
def get_indirect_map_keys(self, location, name):
|
||||
"""Check if 'name' is an indirect map for 'parentmap'."""
|
||||
try:
|
||||
maps = self.ipa_command("automountmap_find", location, {})
|
||||
except Exception: # pylint: disable=broad-except
|
||||
return []
|
||||
|
||||
result = []
|
||||
for check_map in maps.get("result", []):
|
||||
_mapname = check_map['automountmapname'][0]
|
||||
keys = self.ipa_command(
|
||||
"automountkey_find",
|
||||
location,
|
||||
{
|
||||
"automountmapautomountmapname": _mapname,
|
||||
"all": True
|
||||
}
|
||||
)
|
||||
cmp_value = (
|
||||
name if _mapname == "auto.master" else "ldap:{0}".format(name)
|
||||
)
|
||||
result.extend([
|
||||
(location, _mapname, key.get("automountkey")[0])
|
||||
for key in keys.get("result", [])
|
||||
for mount_info in key.get("automountinformation", [])
|
||||
if cmp_value in mount_info
|
||||
])
|
||||
return result
|
||||
|
||||
def check_ipa_params(self):
|
||||
invalid = []
|
||||
@@ -118,15 +165,27 @@ class AutomountMap(IPAAnsibleModule):
|
||||
if len(name) != 1:
|
||||
self.fail_json(msg="Exactly one name must be provided for"
|
||||
" 'state: present'.")
|
||||
mount = self.params_get("mount") or False
|
||||
parentmap = self.params_get("parentmap")
|
||||
if parentmap:
|
||||
if not mount:
|
||||
self.fail_json(
|
||||
msg="Must provide 'mount' parameter for indirect map."
|
||||
)
|
||||
elif parentmap != "auto.master" and mount[0] == "/":
|
||||
self.fail_json(
|
||||
msg="mount point is relative to parent map, "
|
||||
"cannot begin with '/'"
|
||||
)
|
||||
if state == "absent":
|
||||
if len(name) == 0:
|
||||
self.fail_json(msg="At least one 'name' must be provided for"
|
||||
" 'state: absent'")
|
||||
invalid = ["desc"]
|
||||
invalid = ["desc", "parentmap", "mount"]
|
||||
|
||||
self.params_fail_used_invalid(invalid, state)
|
||||
|
||||
def get_args(self, mapname, desc):
|
||||
def get_args(self, mapname, desc, parentmap, mount):
|
||||
# automountmapname is required for all automountmap operations.
|
||||
if not mapname:
|
||||
self.fail_json(msg="automountmapname cannot be None or empty.")
|
||||
@@ -134,6 +193,11 @@ class AutomountMap(IPAAnsibleModule):
|
||||
# An empty string is valid and will clear the attribute.
|
||||
if desc is not None:
|
||||
_args["description"] = desc
|
||||
# indirect map attributes
|
||||
if parentmap is not None:
|
||||
_args["parentmap"] = parentmap
|
||||
if mount is not None:
|
||||
_args["key"] = mount
|
||||
return _args
|
||||
|
||||
def define_ipa_commands(self):
|
||||
@@ -141,28 +205,102 @@ class AutomountMap(IPAAnsibleModule):
|
||||
state = self.params_get("state")
|
||||
location = self.params_get("location")
|
||||
desc = self.params_get("desc")
|
||||
mount = self.params_get("mount")
|
||||
parentmap = self.params_get("parentmap")
|
||||
|
||||
for mapname in name:
|
||||
automountmap = self.get_automountmap(location, mapname)
|
||||
|
||||
is_indirect_map = any([parentmap, mount])
|
||||
|
||||
if state == "present":
|
||||
args = self.get_args(mapname, desc)
|
||||
args = self.get_args(mapname, desc, parentmap, mount)
|
||||
if automountmap is None:
|
||||
self.commands.append([location, "automountmap_add", args])
|
||||
if is_indirect_map:
|
||||
if (
|
||||
parentmap and
|
||||
self.get_automountmap(location, parentmap) is None
|
||||
):
|
||||
self.fail_json(msg="Parent map does not exist.")
|
||||
self.commands.append(
|
||||
[location, "automountmap_add_indirect", args]
|
||||
)
|
||||
else:
|
||||
self.commands.append(
|
||||
[location, "automountmap_add", args]
|
||||
)
|
||||
else:
|
||||
if not compare_args_ipa(self, args, automountmap):
|
||||
has_changes = not compare_args_ipa(
|
||||
self, args, automountmap, ['parentmap', 'key']
|
||||
)
|
||||
if is_indirect_map:
|
||||
map_config = (
|
||||
location, parentmap or "auto.master", mount
|
||||
)
|
||||
indirects = self.get_indirect_map_keys(
|
||||
location, mapname
|
||||
)
|
||||
if map_config not in indirects or has_changes:
|
||||
self.fail_json(
|
||||
msg="Indirect maps can only be created, "
|
||||
"not modified."
|
||||
)
|
||||
elif has_changes:
|
||||
self.commands.append(
|
||||
[location, "automountmap_mod", args]
|
||||
)
|
||||
|
||||
if state == "absent":
|
||||
elif state == "absent":
|
||||
def find_keys(parent_loc, parent_map, parent_key):
|
||||
return self.ipa_command(
|
||||
"automountkey_show",
|
||||
parent_loc,
|
||||
{
|
||||
"automountmapautomountmapname": parent_map,
|
||||
"automountkey": parent_key,
|
||||
}
|
||||
).get("result")
|
||||
|
||||
if automountmap is not None:
|
||||
indirects = self.get_indirect_map_keys(location, mapname)
|
||||
# Remove indirect map configurations for this map
|
||||
self.commands.extend([
|
||||
(
|
||||
ploc,
|
||||
"automountkey_del",
|
||||
{
|
||||
"automountmapautomountmapname": pmap,
|
||||
"automountkey": pkey,
|
||||
}
|
||||
)
|
||||
for ploc, pmap, pkey in indirects
|
||||
if find_keys(ploc, pmap, pkey)
|
||||
])
|
||||
# Remove map
|
||||
self.commands.append([
|
||||
location,
|
||||
"automountmap_del",
|
||||
{"automountmapname": [mapname]}
|
||||
])
|
||||
|
||||
# ensure commands are unique and automountkey commands are
|
||||
# executed first in the list
|
||||
def hashable_dict(dictionaire):
|
||||
return tuple(
|
||||
(k, tuple(v) if isinstance(v, (list, tuple)) else v)
|
||||
for k, v in dictionaire.items()
|
||||
)
|
||||
|
||||
cmds = [
|
||||
(name, cmd, hashable_dict(args))
|
||||
for name, cmd, args in self.commands
|
||||
]
|
||||
self.commands = [
|
||||
(name, cmd, dict(args))
|
||||
for name, cmd, args in
|
||||
sorted(set(cmds), key=lambda cmd: cmd[1])
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
ipa_module = AutomountMap(
|
||||
@@ -184,6 +322,10 @@ def main():
|
||||
required=False,
|
||||
default=None
|
||||
),
|
||||
parentmap=dict(
|
||||
type="str", required=False, default=None
|
||||
),
|
||||
mount=dict(type="str", required=False, default=None),
|
||||
),
|
||||
)
|
||||
changed = False
|
||||
|
||||
574
plugins/modules/ipacert.py
Normal file
574
plugins/modules/ipacert.py
Normal file
@@ -0,0 +1,574 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Sam Morris <sam@robots.org.uk>
|
||||
# Rafael Guterres Jeffman <rjeffman@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2021 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
"metadata_version": "1.0",
|
||||
"supported_by": "community",
|
||||
"status": ["preview"],
|
||||
}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: ipacert
|
||||
short_description: Manage FreeIPA certificates
|
||||
description: Manage FreeIPA certificates
|
||||
extends_documentation_fragment:
|
||||
- ipamodule_base_docs
|
||||
options:
|
||||
csr:
|
||||
description: |
|
||||
X509 certificate signing request, in RFC 7468 PEM encoding.
|
||||
Only available if `state: requested`, required if `csr_file` is not
|
||||
provided.
|
||||
type: str
|
||||
csr_file:
|
||||
description: |
|
||||
Path to file with X509 certificate signing request, in RFC 7468 PEM
|
||||
encoding. Only available if `state: requested`, required if `csr_file`
|
||||
is not provided.
|
||||
type: str
|
||||
principal:
|
||||
description: |
|
||||
Host/service/user principal for the certificate.
|
||||
Required if `state: requested`. Only available if `state: requested`.
|
||||
type: str
|
||||
add:
|
||||
description: |
|
||||
Automatically add the principal if it doesn't exist (service
|
||||
principals only). Only available if `state: requested`.
|
||||
type: bool
|
||||
aliases: ["add_principal"]
|
||||
required: false
|
||||
ca:
|
||||
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`.
|
||||
Required for all states, except `requested`.
|
||||
type: int
|
||||
profile:
|
||||
description: Certificate Profile to use.
|
||||
type: str
|
||||
aliases: ["profile_id"]
|
||||
required: false
|
||||
revocation_reason:
|
||||
description: |
|
||||
Reason for revoking the certificate. Use one of the reason strings,
|
||||
or the corresponding value: "unspecified" (0), "keyCompromise" (1),
|
||||
"cACompromise" (2), "affiliationChanged" (3), "superseded" (4),
|
||||
"cessationOfOperation" (5), "certificateHold" (6), "removeFromCRL" (8),
|
||||
"privilegeWithdrawn" (9), "aACompromise" (10).
|
||||
Use only if `state: revoked`. Required if `state: revoked`.
|
||||
type: raw
|
||||
aliases: ['reason']
|
||||
certificate_out:
|
||||
description: |
|
||||
Write certificate (chain if `chain` is set) to this file, on the target
|
||||
node.. Use only when `state` is `requested` or `retrieved`.
|
||||
type: str
|
||||
required: false
|
||||
state:
|
||||
description: |
|
||||
The state to ensure. `held` is the same as revoke with reason
|
||||
"certificateHold" (6). `released` is the same as `cert-revoke-hold`
|
||||
on IPA CLI, releasing the hold status of a certificate.
|
||||
choices: ["requested", "held", "released", "revoked", "retrieved"]
|
||||
required: true
|
||||
type: str
|
||||
author:
|
||||
- Sam Morris (@yrro)
|
||||
- Rafael Guterres Jeffman (@rjeffman)
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Request a certificate for a web server
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
state: requested
|
||||
csr: |
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIGYMEwCAQAwGTEXMBUGA1UEAwwOZnJlZWlwYSBydWxlcyEwKjAFBgMrZXADIQBs
|
||||
HlqIr4b/XNK+K8QLJKIzfvuNK0buBhLz3LAzY7QDEqAAMAUGAytlcANBAF4oSCbA
|
||||
5aIPukCidnZJdr491G4LBE+URecYXsPknwYb+V+ONnf5ycZHyaFv+jkUBFGFeDgU
|
||||
SYaXm/gF8cDYjQI=
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
principal: HTTP/www.example.com
|
||||
register: cert
|
||||
|
||||
- name: Request certificate for a user, with an appropriate profile.
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
csr: |
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIBejCB5AIBADAQMQ4wDAYDVQQDDAVwaW5reTCBnzANBgkqhkiG9w0BAQEFAAOB
|
||||
jQAwgYkCgYEA7uChccy1Is1FTM0SF23WPYW472E3ozeLh2kzhKR9Ni6FLmeEGgu7
|
||||
/hicR1VwvXHYkNwI1tpW9LqxRVvgr6vheqHySljrBcoRfshfYvKejp03l2327Bfq
|
||||
BNxXqLcHylNEyg8SH0u63bWyxtgoDBfdZwdGAhYuJ+g4ev79J5eYoB0CAwEAAaAr
|
||||
MCkGCSqGSIb3DQEJDjEcMBowGAYHKoZIzlYIAQQNDAtoZWxsbyB3b3JsZDANBgkq
|
||||
hkiG9w0BAQsFAAOBgQADCi5BHDv1mrBFDWqYytFpQ1mrvr/mdax3AYXxNL2UEV8j
|
||||
AqZAFTEnJXL/u1eVQtI1yotqxakyUBN4XZBP2CBgJRO93Mtry8cgvU1sPdU8Mavx
|
||||
5gSnlP74Hio2ziscWWydlxpYxFx0gkKvu+0nyIpz954SVYwQ2wwk5FRqZnxI5w==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
principal: pinky
|
||||
profile_id: IECUserRoles
|
||||
state: requested
|
||||
|
||||
- name: Temporarily hold a certificate
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
serial_number: 12345
|
||||
state: held
|
||||
|
||||
- name: Remove a certificate hold
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
state: released
|
||||
serial_number: 12345
|
||||
|
||||
- name: Permanently revoke a certificate issued by a lightweight sub-CA
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
state: revoked
|
||||
ca: vpn-ca
|
||||
serial_number: 0x98765
|
||||
reason: keyCompromise
|
||||
|
||||
- name: Retrieve a certificate
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
serial_number: 12345
|
||||
state: retrieved
|
||||
register: cert_retrieved
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
certificate:
|
||||
description: Certificate fields and data.
|
||||
returned: |
|
||||
if `state` is `requested` or `retrived` and `certificate_out`
|
||||
is not defined.
|
||||
type: dict
|
||||
contains:
|
||||
certificate:
|
||||
description: |
|
||||
Issued X509 certificate in PEM encoding. Will include certificate
|
||||
chain if `chain: true` is used.
|
||||
type: list
|
||||
elements: str
|
||||
returned: always
|
||||
issuer:
|
||||
description: X509 distinguished name of issuer.
|
||||
type: str
|
||||
sample: CN=Certificate Authority,O=EXAMPLE.COM
|
||||
returned: always
|
||||
serial_number:
|
||||
description: Serial number of the issued certificate.
|
||||
type: int
|
||||
sample: 902156300
|
||||
returned: always
|
||||
valid_not_after:
|
||||
description: |
|
||||
Time when issued certificate ceases to be valid,
|
||||
in GeneralizedTime format (YYYYMMDDHHMMSSZ).
|
||||
type: str
|
||||
returned: always
|
||||
valid_not_before:
|
||||
description: |
|
||||
Time when issued certificate becomes valid, in
|
||||
GeneralizedTime format (YYYYMMDDHHMMSSZ).
|
||||
type: str
|
||||
returned: always
|
||||
subject:
|
||||
description: X509 distinguished name of certificate subject.
|
||||
type: str
|
||||
sample: CN=www.example.com,O=EXAMPLE.COM
|
||||
returned: always
|
||||
san_dnsname:
|
||||
description: X509 Subject Alternative Name.
|
||||
type: list
|
||||
elements: str
|
||||
sample: ['www.example.com', 'other.example.com']
|
||||
returned: |
|
||||
when DNSNames are present in the Subject Alternative Name
|
||||
extension of the issued certificate.
|
||||
revoked:
|
||||
description: Revoked status of the certificate.
|
||||
type: bool
|
||||
returned: always
|
||||
owner_user:
|
||||
description: The username that owns the certificate.
|
||||
type: str
|
||||
returned: when `state` is `retrieved`
|
||||
owner_host:
|
||||
description: The host that owns the certificate.
|
||||
type: str
|
||||
returned: when `state` is `retrieved`
|
||||
owner_service:
|
||||
description: The service that owns the certificate.
|
||||
type: str
|
||||
returned: when `state` is `retrieved`
|
||||
"""
|
||||
|
||||
import base64
|
||||
import time
|
||||
import ssl
|
||||
|
||||
from ansible.module_utils import six
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.ansible_freeipa_module import (
|
||||
IPAAnsibleModule, certificate_loader, write_certificate_list,
|
||||
)
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
# Reasons are defined in RFC 5280 sec. 5.3.1; removeFromCRL is not present in
|
||||
# this list; run the module with state=released instead.
|
||||
REVOCATION_REASONS = {
|
||||
'unspecified': 0,
|
||||
'keyCompromise': 1,
|
||||
'cACompromise': 2,
|
||||
'affiliationChanged': 3,
|
||||
'superseded': 4,
|
||||
'cessationOfOperation': 5,
|
||||
'certificateHold': 6,
|
||||
'removeFromCRL': 8,
|
||||
'privilegeWithdrawn': 9,
|
||||
'aACompromise': 10,
|
||||
}
|
||||
|
||||
|
||||
def gen_args(
|
||||
module, principal=None, add_principal=None, ca=None, chain=None,
|
||||
profile=None, certificate_out=None, reason=None
|
||||
):
|
||||
args = {}
|
||||
if principal is not None:
|
||||
args['principal'] = principal
|
||||
if add_principal is not None:
|
||||
args['add'] = add_principal
|
||||
if ca is not None:
|
||||
args['cacn'] = ca
|
||||
if profile is not None:
|
||||
args['profile_id'] = profile
|
||||
if certificate_out is not None:
|
||||
args['out'] = certificate_out
|
||||
if chain:
|
||||
args['chain'] = True
|
||||
if ca:
|
||||
args['cacn'] = ca
|
||||
if reason is not None:
|
||||
args['revocation_reason'] = get_revocation_reason(module, reason)
|
||||
return args
|
||||
|
||||
|
||||
def get_revocation_reason(module, reason):
|
||||
"""Ensure revocation reasion is a valid integer code."""
|
||||
reason_int = -1
|
||||
|
||||
try:
|
||||
reason_int = int(reason)
|
||||
except ValueError:
|
||||
reason_int = REVOCATION_REASONS.get(reason, -1)
|
||||
|
||||
if reason_int not in REVOCATION_REASONS.values():
|
||||
module.fail_json(msg="Invalid revocation reason: %s" % reason)
|
||||
|
||||
return reason_int
|
||||
|
||||
|
||||
def parse_cert_timestamp(dt):
|
||||
"""Ensure time is in GeneralizedTime format (YYYYMMDDHHMMSSZ)."""
|
||||
return time.strftime(
|
||||
"%Y%m%d%H%M%SZ",
|
||||
time.strptime(dt, "%a %b %d %H:%M:%S %Y UTC")
|
||||
)
|
||||
|
||||
|
||||
def result_handler(_module, result, _command, _name, _args, exit_args, chain):
|
||||
"""Split certificate into fields."""
|
||||
if chain:
|
||||
exit_args['certificate'] = [
|
||||
ssl.DER_cert_to_PEM_cert(c)
|
||||
for c in result['result'].get('certificate_chain', [])
|
||||
]
|
||||
else:
|
||||
exit_args['certificate'] = [
|
||||
ssl.DER_cert_to_PEM_cert(
|
||||
base64.b64decode(result['result']['certificate'])
|
||||
)
|
||||
]
|
||||
|
||||
exit_args['san_dnsname'] = [
|
||||
str(dnsname)
|
||||
for dnsname in result['result'].get('san_dnsname', [])
|
||||
]
|
||||
|
||||
exit_args.update({
|
||||
key: result['result'][key]
|
||||
for key in [
|
||||
'issuer', 'subject', 'serial_number',
|
||||
'revoked', 'revocation_reason'
|
||||
]
|
||||
if key in result['result']
|
||||
})
|
||||
exit_args.update({
|
||||
key: result['result'][key][0]
|
||||
for key in ['owner_user', 'owner_host', 'owner_service']
|
||||
if key in result['result']
|
||||
})
|
||||
|
||||
exit_args.update({
|
||||
key: parse_cert_timestamp(result['result'][key])
|
||||
for key in ['valid_not_after', 'valid_not_before']
|
||||
if key in result['result']
|
||||
})
|
||||
|
||||
|
||||
def do_cert_request(
|
||||
module, csr, principal, add_principal=None, ca=None, profile=None,
|
||||
chain=None, certificate_out=None
|
||||
):
|
||||
"""Request a certificate."""
|
||||
args = gen_args(
|
||||
module, principal=principal, ca=ca, chain=chain,
|
||||
add_principal=add_principal, profile=profile,
|
||||
)
|
||||
exit_args = {}
|
||||
commands = [[to_text(csr), "cert_request", args]]
|
||||
changed = module.execute_ipa_commands(
|
||||
commands,
|
||||
result_handler=result_handler,
|
||||
exit_args=exit_args,
|
||||
chain=chain
|
||||
)
|
||||
|
||||
if certificate_out is not None:
|
||||
certs = (
|
||||
certificate_loader(cert.encode("utf-8"))
|
||||
for cert in exit_args['certificate']
|
||||
)
|
||||
write_certificate_list(certs, certificate_out)
|
||||
exit_args = {}
|
||||
|
||||
return changed, exit_args
|
||||
|
||||
|
||||
def do_cert_revoke(ansible_module, serial_number, reason=None, ca=None):
|
||||
"""Revoke a certificate."""
|
||||
_ign, cert = do_cert_retrieve(ansible_module, serial_number, ca)
|
||||
if not cert or cert.get('revoked', False):
|
||||
return False, cert
|
||||
|
||||
args = gen_args(ansible_module, ca=ca, reason=reason)
|
||||
|
||||
commands = [[serial_number, "cert_revoke", args]]
|
||||
changed = ansible_module.execute_ipa_commands(commands)
|
||||
|
||||
return changed, cert
|
||||
|
||||
|
||||
def do_cert_release(ansible_module, serial_number, ca=None):
|
||||
"""Release hold on certificate."""
|
||||
_ign, cert = do_cert_retrieve(ansible_module, serial_number, ca)
|
||||
revoked = cert.get('revoked', True)
|
||||
reason = cert.get('revocation_reason', -1)
|
||||
if cert and not revoked:
|
||||
return False, cert
|
||||
|
||||
if revoked and reason != 6: # can only release held certificates
|
||||
ansible_module.fail_json(
|
||||
msg="Cannot release hold on certificate revoked with"
|
||||
" reason: %d" % reason
|
||||
)
|
||||
args = gen_args(ansible_module, ca=ca)
|
||||
|
||||
commands = [[serial_number, "cert_remove_hold", args]]
|
||||
changed = ansible_module.execute_ipa_commands(commands)
|
||||
|
||||
return changed, cert
|
||||
|
||||
|
||||
def do_cert_retrieve(
|
||||
module, serial_number, ca=None, chain=None, outfile=None
|
||||
):
|
||||
"""Retrieve a certificate with 'cert-show'."""
|
||||
args = gen_args(module, ca=ca, chain=chain, certificate_out=outfile)
|
||||
exit_args = {}
|
||||
commands = [[serial_number, "cert_show", args]]
|
||||
module.execute_ipa_commands(
|
||||
commands,
|
||||
result_handler=result_handler,
|
||||
exit_args=exit_args,
|
||||
chain=chain,
|
||||
)
|
||||
if outfile is not None:
|
||||
exit_args = {}
|
||||
|
||||
return False, exit_args
|
||||
|
||||
|
||||
def main():
|
||||
ansible_module = IPAAnsibleModule(
|
||||
argument_spec=dict(
|
||||
# requested
|
||||
csr=dict(type="str"),
|
||||
csr_file=dict(type="str"),
|
||||
principal=dict(type="str"),
|
||||
add_principal=dict(type="bool", required=False, aliases=["add"]),
|
||||
profile_id=dict(type="str", aliases=["profile"], required=False),
|
||||
# revoked
|
||||
revocation_reason=dict(type="raw", aliases=["reason"]),
|
||||
# general
|
||||
serial_number=dict(type="int"),
|
||||
ca=dict(type="str"),
|
||||
chain=dict(type="bool", required=False),
|
||||
certificate_out=dict(type="str", required=False),
|
||||
# state
|
||||
state=dict(
|
||||
type="str",
|
||||
required=True,
|
||||
choices=[
|
||||
"requested", "held", "released", "revoked", "retrieved"
|
||||
]
|
||||
),
|
||||
),
|
||||
mutually_exclusive=[["csr", "csr_file"]],
|
||||
required_if=[
|
||||
('state', 'requested', ['principal']),
|
||||
('state', 'retrieved', ['serial_number']),
|
||||
('state', 'held', ['serial_number']),
|
||||
('state', 'released', ['serial_number']),
|
||||
('state', 'revoked', ['serial_number', 'revocation_reason']),
|
||||
],
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
|
||||
# Get parameters
|
||||
|
||||
# requested
|
||||
csr = ansible_module.params_get("csr")
|
||||
csr_file = ansible_module.params_get("csr_file")
|
||||
principal = ansible_module.params_get("principal")
|
||||
add_principal = ansible_module.params_get("add_principal")
|
||||
profile = ansible_module.params_get("profile_id")
|
||||
|
||||
# revoked
|
||||
reason = ansible_module.params_get("revocation_reason")
|
||||
|
||||
# general
|
||||
serial_number = ansible_module.params.get("serial_number")
|
||||
ca = ansible_module.params_get("ca")
|
||||
chain = ansible_module.params_get("chain")
|
||||
certificate_out = ansible_module.params_get("certificate_out")
|
||||
|
||||
# state
|
||||
state = ansible_module.params_get("state")
|
||||
|
||||
# Check parameters
|
||||
|
||||
if ansible_module.params_get("ipaapi_context") == "server":
|
||||
ansible_module.fail_json(
|
||||
msg="Context 'server' for ipacert is not yet supported."
|
||||
)
|
||||
|
||||
invalid = []
|
||||
if state == "requested":
|
||||
invalid = ["serial_number", "revocation_reason"]
|
||||
if csr is None and csr_file is None:
|
||||
ansible_module.fail_json(
|
||||
msg="Required 'csr' or 'csr_file' with 'state: requested'.")
|
||||
else:
|
||||
invalid = [
|
||||
"csr", "principal", "add_principal", "profile"
|
||||
"certificate_out"
|
||||
]
|
||||
if state in ["released", "held"]:
|
||||
invalid.extend(["revocation_reason", "certificate_out", "chain"])
|
||||
if state == "retrieved":
|
||||
invalid.append("revocation_reason")
|
||||
if state == "revoked":
|
||||
invalid.extend(["certificate_out", "chain"])
|
||||
elif state == "held":
|
||||
reason = 6 # certificateHold
|
||||
|
||||
ansible_module.params_fail_used_invalid(invalid, state)
|
||||
|
||||
# Init
|
||||
|
||||
changed = False
|
||||
exit_args = {}
|
||||
|
||||
# Connect to IPA API
|
||||
# If executed on 'server' contexot, cert plugin uses the IPA RA agent
|
||||
# TLS client certificate/key, which users are not able to access,
|
||||
# resulting in a 'permission denied' exception when attempting to connect
|
||||
# the CA service. Therefore 'client' context in forced for this module.
|
||||
with ansible_module.ipa_connect(context="client"):
|
||||
|
||||
if state == "requested":
|
||||
if csr_file is not None:
|
||||
with open(csr_file, "rt") as csr_in:
|
||||
csr = "".join(csr_in.readlines())
|
||||
changed, exit_args = do_cert_request(
|
||||
ansible_module,
|
||||
csr,
|
||||
principal,
|
||||
add_principal,
|
||||
ca,
|
||||
profile,
|
||||
chain,
|
||||
certificate_out
|
||||
)
|
||||
|
||||
elif state in ("held", "revoked"):
|
||||
changed, exit_args = do_cert_revoke(
|
||||
ansible_module, serial_number, reason, ca)
|
||||
|
||||
elif state == "released":
|
||||
changed, exit_args = do_cert_release(
|
||||
ansible_module, serial_number, ca)
|
||||
|
||||
elif state == "retrieved":
|
||||
changed, exit_args = do_cert_retrieve(
|
||||
ansible_module, serial_number, ca, chain, certificate_out)
|
||||
|
||||
# Done
|
||||
|
||||
ansible_module.exit_json(changed=changed, certificate=exit_args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -160,7 +160,8 @@ options:
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
choices: ["password", "radius", "otp", "disabled", ""]
|
||||
choices: ["password", "radius", "otp", "pkinit", "hardened", "idp",
|
||||
"disabled", ""]
|
||||
aliases: ["ipauserauthtype"]
|
||||
ca_renewal_master_server:
|
||||
description: Renewal master for IPA certificate authority.
|
||||
@@ -357,8 +358,7 @@ def get_netbios_name(module):
|
||||
_result = module.ipa_command_no_name("trustconfig_show", {"all": True})
|
||||
except Exception: # pylint: disable=broad-except
|
||||
return None
|
||||
else:
|
||||
return _result["result"]["ipantflatname"][0]
|
||||
return _result["result"]["ipantflatname"][0]
|
||||
|
||||
|
||||
def is_enable_sid(module):
|
||||
@@ -425,6 +425,7 @@ def main():
|
||||
choices=["MS-PAC", "PAD", "nfs:NONE", ""]),
|
||||
user_auth_type=dict(type="list", elements="str", required=False,
|
||||
choices=["password", "radius", "otp",
|
||||
"pkinit", "hardened", "idp",
|
||||
"disabled", ""],
|
||||
aliases=["ipauserauthtype"]),
|
||||
ca_renewal_master_server=dict(type="str", required=False),
|
||||
@@ -469,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
|
||||
@@ -525,6 +526,15 @@ def main():
|
||||
result = config_show(ansible_module)
|
||||
|
||||
if params:
|
||||
# Verify ipauserauthtype(s)
|
||||
if "ipauserauthtype" in params and params["ipauserauthtype"]:
|
||||
_invalid = ansible_module.ipa_command_invalid_param_choices(
|
||||
"config_mod", "ipauserauthtype", params["ipauserauthtype"])
|
||||
if _invalid:
|
||||
ansible_module.fail_json(
|
||||
msg="The use of userauthtype '%s' is not "
|
||||
"supported by your IPA version" % "','".join(_invalid))
|
||||
|
||||
enable_sid = params.get("enable_sid")
|
||||
sid_is_enabled = has_enable_sid and is_enable_sid(ansible_module)
|
||||
|
||||
@@ -609,7 +619,7 @@ def main():
|
||||
# boolean values, so we need to convert it to str
|
||||
# for comparison.
|
||||
# See: https://github.com/freeipa/freeipa/pull/6294
|
||||
exit_args[k] = (str(value[0]).upper() == "TRUE")
|
||||
exit_args[k] = str(value[0]).upper() == "TRUE"
|
||||
else:
|
||||
if arg_type not in type_map:
|
||||
raise ValueError(
|
||||
|
||||
@@ -134,8 +134,7 @@ def find_delegation(module, name):
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# An exception is raised if delegation name is not found.
|
||||
return None
|
||||
else:
|
||||
return _result["result"]
|
||||
return _result["result"]
|
||||
|
||||
|
||||
def gen_args(permission, attribute, membergroup, group):
|
||||
@@ -181,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")
|
||||
@@ -234,6 +233,7 @@ def main():
|
||||
|
||||
commands = []
|
||||
for name in names:
|
||||
args = {}
|
||||
# Make sure delegation exists
|
||||
res_find = find_delegation(ansible_module, name)
|
||||
|
||||
@@ -245,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":
|
||||
@@ -266,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":
|
||||
@@ -289,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(
|
||||
@@ -258,7 +260,7 @@ def main():
|
||||
invalid = [
|
||||
"forwarders", "forwardpolicy", "skip_overlap_check", "permission"
|
||||
]
|
||||
wants_enable = (state == "enabled")
|
||||
wants_enable = state == "enabled"
|
||||
|
||||
if operation == "del":
|
||||
invalid = [
|
||||
|
||||
@@ -1394,15 +1394,16 @@ def gen_args(entry):
|
||||
|
||||
if record_value is not None:
|
||||
record_type = entry['record_type']
|
||||
rec = "{}record".format(record_type.lower())
|
||||
rec = "{0}record".format(record_type.lower())
|
||||
args[rec] = ensure_data_is_list(record_value)
|
||||
|
||||
else:
|
||||
for field in _RECORD_FIELDS:
|
||||
record_value = entry.get(field) or entry.get("%sord" % field)
|
||||
if record_value is not None:
|
||||
# pylint: disable=use-maxsplit-arg
|
||||
record_type = field.split('_')[0]
|
||||
rec = "{}record".format(record_type.lower())
|
||||
rec = "{0}record".format(record_type.lower())
|
||||
args[rec] = ensure_data_is_list(record_value)
|
||||
|
||||
records = {
|
||||
@@ -1452,7 +1453,7 @@ def define_commands_for_present_state(module, zone_name, entry, res_find):
|
||||
else:
|
||||
# Create reverse records for existing records
|
||||
for ipv in ['a', 'aaaa']:
|
||||
record = ('%srecord' % ipv)
|
||||
record = '%srecord' % ipv
|
||||
if record in args and ('%s_extra_create_reverse' % ipv) in args:
|
||||
cmds = create_reverse_ip_record(
|
||||
module, zone_name, name, args[record])
|
||||
@@ -1604,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),
|
||||
)
|
||||
|
||||
@@ -41,8 +41,93 @@ options:
|
||||
description: The group name
|
||||
type: list
|
||||
elements: str
|
||||
required: true
|
||||
required: false
|
||||
aliases: ["cn"]
|
||||
groups:
|
||||
description: The list of group dicts (internally gid).
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
name:
|
||||
description: The group (internally gid).
|
||||
type: str
|
||||
required: true
|
||||
aliases: ["cn"]
|
||||
description:
|
||||
description: The group description
|
||||
type: str
|
||||
required: false
|
||||
gid:
|
||||
description: The GID
|
||||
type: int
|
||||
required: false
|
||||
aliases: ["gidnumber"]
|
||||
nonposix:
|
||||
description: Create as a non-POSIX group
|
||||
required: false
|
||||
type: bool
|
||||
external:
|
||||
description: Allow adding external non-IPA members from trusted domains
|
||||
required: false
|
||||
type: bool
|
||||
posix:
|
||||
description:
|
||||
Create a non-POSIX group or change a non-POSIX to a posix group.
|
||||
required: false
|
||||
type: bool
|
||||
nomembers:
|
||||
description: Suppress processing of membership attributes
|
||||
required: false
|
||||
type: bool
|
||||
user:
|
||||
description: List of user names assigned to this group.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
group:
|
||||
description: List of group names assigned to this group.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
service:
|
||||
description:
|
||||
- List of service names assigned to this group.
|
||||
- Only usable with IPA versions 4.7 and up.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
membermanager_user:
|
||||
description:
|
||||
- List of member manager users assigned to this group.
|
||||
- Only usable with IPA versions 4.8.4 and up.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
membermanager_group:
|
||||
description:
|
||||
- List of member manager groups assigned to this group.
|
||||
- Only usable with IPA versions 4.8.4 and up.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
externalmember:
|
||||
description:
|
||||
- List of members of a trusted domain in DOM\\name or name@domain form.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["ipaexternalmember", "external_member"]
|
||||
idoverrideuser:
|
||||
description:
|
||||
- User ID overrides to add
|
||||
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
|
||||
@@ -118,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)
|
||||
"""
|
||||
@@ -144,6 +234,14 @@ EXAMPLES = """
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: appops
|
||||
|
||||
# Create multiple groups ops, sysops
|
||||
- ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: ops
|
||||
gidnumber: 1234
|
||||
- name: sysops
|
||||
|
||||
# Add user member pinky to group sysops
|
||||
- ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
@@ -160,7 +258,7 @@ EXAMPLES = """
|
||||
user:
|
||||
- brain
|
||||
|
||||
# Add group members sysops and appops to group sysops
|
||||
# Add group members sysops and appops to group ops
|
||||
- ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: ops
|
||||
@@ -168,6 +266,24 @@ EXAMPLES = """
|
||||
- sysops
|
||||
- appops
|
||||
|
||||
# Add user and group members to groups sysops and appops
|
||||
- ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: sysops
|
||||
user:
|
||||
- user1
|
||||
- name: appops
|
||||
group:
|
||||
- group2
|
||||
|
||||
# Rename a group
|
||||
- ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: oldname
|
||||
rename: newestname
|
||||
state: renamed
|
||||
|
||||
# Create a non-POSIX group
|
||||
- ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
@@ -189,7 +305,16 @@ EXAMPLES = """
|
||||
- WINIPA\\Web Users
|
||||
- WINIPA\\Developers
|
||||
|
||||
# Remove goups sysops, appops, ops and nongroup
|
||||
# Create multiple non-POSIX and external groups
|
||||
- ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: nongroup
|
||||
nonposix: true
|
||||
- name: extgroup
|
||||
external: true
|
||||
|
||||
# Remove groups sysops, appops, ops and nongroup
|
||||
- ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: sysops,appops,ops, nongroup
|
||||
@@ -203,6 +328,20 @@ from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.ansible_freeipa_module import \
|
||||
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, \
|
||||
gen_add_list, gen_intersection_list, api_check_param
|
||||
from ansible.module_utils import six
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
# Ensuring (adding) several groups with mixed types external, nonposix
|
||||
# and posix require to have a fix in IPA:
|
||||
# FreeIPA issue: https://pagure.io/freeipa/issue/9349
|
||||
# FreeIPA fix: https://github.com/freeipa/freeipa/pull/6741
|
||||
try:
|
||||
from ipaserver.plugins import baseldap
|
||||
except ImportError:
|
||||
FIX_6741_DEEPCOPY_OBJECTCLASSES = False
|
||||
else:
|
||||
FIX_6741_DEEPCOPY_OBJECTCLASSES = \
|
||||
"deepcopy" in baseldap.LDAPObject.__json__.__code__.co_names
|
||||
|
||||
|
||||
def find_group(module, name):
|
||||
@@ -257,6 +396,24 @@ def gen_member_args(user, group, service, externalmember, idoverrideuser):
|
||||
return _args
|
||||
|
||||
|
||||
def check_parameters(module, state, action):
|
||||
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)
|
||||
|
||||
|
||||
def is_external_group(res_find):
|
||||
"""Verify if the result group is an external group."""
|
||||
return res_find and 'ipaexternalgroup' in res_find['objectclass']
|
||||
@@ -285,45 +442,65 @@ def check_objectclass_args(module, res_find, posix, external):
|
||||
|
||||
|
||||
def main():
|
||||
group_spec = dict(
|
||||
# present
|
||||
description=dict(type="str", default=None),
|
||||
gid=dict(type="int", aliases=["gidnumber"], default=None),
|
||||
nonposix=dict(required=False, type='bool', default=None),
|
||||
external=dict(required=False, type='bool', default=None),
|
||||
posix=dict(required=False, type='bool', default=None),
|
||||
nomembers=dict(required=False, type='bool', default=None),
|
||||
user=dict(required=False, type='list', elements="str",
|
||||
default=None),
|
||||
group=dict(required=False, type='list', elements="str",
|
||||
default=None),
|
||||
service=dict(required=False, type='list', elements="str",
|
||||
default=None),
|
||||
idoverrideuser=dict(required=False, type='list', elements="str",
|
||||
default=None),
|
||||
membermanager_user=dict(required=False, type='list',
|
||||
elements="str", default=None),
|
||||
membermanager_group=dict(required=False, type='list',
|
||||
elements="str", default=None),
|
||||
externalmember=dict(required=False, type='list', elements="str",
|
||||
default=None,
|
||||
aliases=[
|
||||
"ipaexternalmember",
|
||||
"external_member"
|
||||
]),
|
||||
rename=dict(type="str", required=False, default=None,
|
||||
aliases=["new_name"]),
|
||||
)
|
||||
ansible_module = IPAAnsibleModule(
|
||||
argument_spec=dict(
|
||||
# general
|
||||
name=dict(type="list", elements="str", aliases=["cn"],
|
||||
required=True),
|
||||
# present
|
||||
description=dict(type="str", default=None),
|
||||
gid=dict(type="int", aliases=["gidnumber"], default=None),
|
||||
nonposix=dict(required=False, type='bool', default=None),
|
||||
external=dict(required=False, type='bool', default=None),
|
||||
posix=dict(required=False, type='bool', default=None),
|
||||
nomembers=dict(required=False, type='bool', default=None),
|
||||
user=dict(required=False, type='list', elements="str",
|
||||
default=None),
|
||||
group=dict(required=False, type='list', elements="str",
|
||||
default=None),
|
||||
service=dict(required=False, type='list', elements="str",
|
||||
default=None),
|
||||
idoverrideuser=dict(required=False, type='list', elements="str",
|
||||
default=None),
|
||||
membermanager_user=dict(required=False, type='list',
|
||||
elements="str", default=None),
|
||||
membermanager_group=dict(required=False, type='list',
|
||||
elements="str", default=None),
|
||||
externalmember=dict(required=False, type='list', elements="str",
|
||||
default=None,
|
||||
aliases=[
|
||||
"ipaexternalmember",
|
||||
"external_member"
|
||||
]),
|
||||
default=None, required=False),
|
||||
groups=dict(type="list",
|
||||
default=None,
|
||||
options=dict(
|
||||
# Here name is a simple string
|
||||
name=dict(type="str", required=True,
|
||||
aliases=["cn"]),
|
||||
# Add group specific parameters
|
||||
**group_spec
|
||||
),
|
||||
elements='dict',
|
||||
required=False),
|
||||
# general
|
||||
action=dict(type="str", default="group",
|
||||
choices=["member", "group"]),
|
||||
# state
|
||||
state=dict(type="str", default="present",
|
||||
choices=["present", "absent"]),
|
||||
choices=["present", "absent", "renamed"]),
|
||||
|
||||
# Add group specific parameters for simple use case
|
||||
**group_spec
|
||||
),
|
||||
# It does not make sense to set posix, nonposix or external at the
|
||||
# same time
|
||||
mutually_exclusive=[['posix', 'nonposix', 'external']],
|
||||
mutually_exclusive=[['posix', 'nonposix', 'external'],
|
||||
["name", "groups"]],
|
||||
required_one_of=[["name", "groups"]],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
@@ -333,6 +510,7 @@ def main():
|
||||
|
||||
# general
|
||||
names = ansible_module.params_get("name")
|
||||
groups = ansible_module.params_get("groups")
|
||||
|
||||
# present
|
||||
description = ansible_module.params_get("description")
|
||||
@@ -342,43 +520,67 @@ 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
|
||||
invalid = []
|
||||
|
||||
if state == "present":
|
||||
if len(names) != 1:
|
||||
if (names is None or len(names) < 1) and \
|
||||
(groups is None or len(groups) < 1):
|
||||
ansible_module.fail_json(msg="At least one name or groups is required")
|
||||
|
||||
if state in ["present", "renamed"]:
|
||||
if names is not None and len(names) != 1:
|
||||
what = "renamed" if state == "renamed" else "added"
|
||||
ansible_module.fail_json(
|
||||
msg="Only one group can be added at a time.")
|
||||
if action == "member":
|
||||
invalid = ["description", "gid", "posix", "nonposix", "external",
|
||||
"nomembers"]
|
||||
msg="Only one group can be %s at a time using 'name'." % what)
|
||||
|
||||
if state == "absent":
|
||||
if len(names) < 1:
|
||||
ansible_module.fail_json(
|
||||
msg="No name given.")
|
||||
invalid = ["description", "gid", "posix", "nonposix", "external",
|
||||
"nomembers"]
|
||||
if action == "group":
|
||||
invalid.extend(["user", "group", "service", "externalmember"])
|
||||
|
||||
ansible_module.params_fail_used_invalid(invalid, state, action)
|
||||
check_parameters(ansible_module, state, action)
|
||||
|
||||
if external is False:
|
||||
ansible_module.fail_json(
|
||||
msg="group can not be non-external")
|
||||
|
||||
# Ensuring (adding) several groups with mixed types external, nonposix
|
||||
# and posix require to have a fix in IPA:
|
||||
#
|
||||
# FreeIPA issue: https://pagure.io/freeipa/issue/9349
|
||||
# FreeIPA fix: https://github.com/freeipa/freeipa/pull/6741
|
||||
#
|
||||
# The simple solution is to switch to client context for ensuring
|
||||
# several groups simply if the user was not explicitly asking for
|
||||
# the server context no matter if mixed types are used.
|
||||
context = None
|
||||
if state == "present" and groups is not None and len(groups) > 1 \
|
||||
and not FIX_6741_DEEPCOPY_OBJECTCLASSES:
|
||||
_context = ansible_module.params_get("ipaapi_context")
|
||||
if _context is None:
|
||||
context = "client"
|
||||
ansible_module.debug(
|
||||
"Switching to client context due to an unfixed issue in "
|
||||
"your IPA version: https://pagure.io/freeipa/issue/9349")
|
||||
elif _context == "server":
|
||||
ansible_module.fail_json(
|
||||
msg="Ensuring several groups with server context is not "
|
||||
"supported by your IPA version: "
|
||||
"https://pagure.io/freeipa/issue/9349")
|
||||
|
||||
# Use groups if names is None
|
||||
if groups is not None:
|
||||
names = groups
|
||||
|
||||
# Init
|
||||
|
||||
changed = False
|
||||
@@ -389,7 +591,7 @@ def main():
|
||||
posix = not nonposix
|
||||
|
||||
# Connect to IPA API
|
||||
with ansible_module.ipa_connect():
|
||||
with ansible_module.ipa_connect(context=context):
|
||||
|
||||
has_add_member_service = ansible_module.ipa_command_param_exists(
|
||||
"group_add_member", "service")
|
||||
@@ -415,8 +617,62 @@ def main():
|
||||
"supported by your IPA version")
|
||||
|
||||
commands = []
|
||||
group_set = set()
|
||||
|
||||
for group_name in names:
|
||||
if isinstance(group_name, dict):
|
||||
name = group_name.get("name")
|
||||
if name in group_set:
|
||||
ansible_module.fail_json(
|
||||
msg="group '%s' is used more than once" % name)
|
||||
group_set.add(name)
|
||||
# present
|
||||
description = group_name.get("description")
|
||||
gid = group_name.get("gid")
|
||||
nonposix = group_name.get("nonposix")
|
||||
external = group_name.get("external")
|
||||
idoverrideuser = group_name.get("idoverrideuser")
|
||||
posix = group_name.get("posix")
|
||||
# Check mutually exclusive condition for multiple groups
|
||||
# creation. It's not possible to check it with
|
||||
# `mutually_exclusive` argument in `IPAAnsibleModule` class
|
||||
# because it accepts only (list[str] or list[list[str]]). Here
|
||||
# we need to loop over all groups and fail on mutually
|
||||
# exclusive ones.
|
||||
if all((posix, nonposix)) or\
|
||||
all((posix, external)) or\
|
||||
all((nonposix, external)):
|
||||
ansible_module.fail_json(
|
||||
msg="parameters are mutually exclusive for group "
|
||||
"`{0}`: posix|nonposix|external".format(name))
|
||||
# Duplicating the condition for multiple group creation
|
||||
if external is False:
|
||||
ansible_module.fail_json(
|
||||
msg="group can not be non-external")
|
||||
# If nonposix is used, set posix as not nonposix
|
||||
if nonposix is not None:
|
||||
posix = not nonposix
|
||||
user = group_name.get("user")
|
||||
group = group_name.get("group")
|
||||
service = group_name.get("service")
|
||||
membermanager_user = group_name.get("membermanager_user")
|
||||
membermanager_group = group_name.get("membermanager_group")
|
||||
externalmember = group_name.get("externalmember")
|
||||
nomembers = group_name.get("nomembers")
|
||||
rename = group_name.get("rename")
|
||||
|
||||
check_parameters(ansible_module, state, action)
|
||||
|
||||
elif (
|
||||
isinstance(
|
||||
group_name, (str, unicode) # pylint: disable=W0012,E0606
|
||||
)
|
||||
):
|
||||
name = group_name
|
||||
else:
|
||||
ansible_module.fail_json(msg="Group '%s' is not valid" %
|
||||
repr(group_name))
|
||||
|
||||
for name in names:
|
||||
# Make sure group exists
|
||||
res_find = find_group(ansible_module, name)
|
||||
|
||||
@@ -569,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)
|
||||
|
||||
@@ -593,10 +854,12 @@ def main():
|
||||
del_member_args["service"] = service_del
|
||||
|
||||
if is_external_group(res_find):
|
||||
add_member_args["ipaexternalmember"] = \
|
||||
externalmember_add
|
||||
del_member_args["ipaexternalmember"] = \
|
||||
externalmember_del
|
||||
if len(externalmember_add) > 0:
|
||||
add_member_args["ipaexternalmember"] = \
|
||||
externalmember_add
|
||||
if len(externalmember_del) > 0:
|
||||
del_member_args["ipaexternalmember"] = \
|
||||
externalmember_del
|
||||
elif externalmember or external:
|
||||
ansible_module.fail_json(
|
||||
msg="Cannot add external members to a "
|
||||
@@ -641,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
|
||||
|
||||
|
||||
@@ -186,7 +186,16 @@ def find_hbacrule(module, name):
|
||||
module.fail_json(
|
||||
msg="There is more than one hbacrule '%s'" % (name))
|
||||
elif len(_result["result"]) == 1:
|
||||
return _result["result"][0]
|
||||
res = _result["result"][0]
|
||||
# hbacsvcgroup names are converted to lower case while creation with
|
||||
# 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:
|
||||
res[_member] = [item.lower() for item in res[_member]]
|
||||
return res
|
||||
|
||||
return None
|
||||
|
||||
@@ -390,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(
|
||||
|
||||
@@ -146,21 +146,6 @@ def gen_member_args(hbacsvc):
|
||||
return _args
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def result_handler(module, result, command, name, args, errors):
|
||||
# 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 "member" in result["failed"]:
|
||||
failed = result["failed"]["member"]
|
||||
for member_type in failed:
|
||||
for member, failure in failed[member_type]:
|
||||
if "already a member" not in failure \
|
||||
and "not a member" not in failure:
|
||||
errors.append("%s: %s %s: %s" % (
|
||||
command, member_type, member, failure))
|
||||
|
||||
|
||||
def main():
|
||||
ansible_module = IPAAnsibleModule(
|
||||
argument_spec=dict(
|
||||
@@ -303,7 +288,8 @@ def main():
|
||||
}])
|
||||
|
||||
# Execute commands
|
||||
changed = ansible_module.execute_ipa_commands(commands, result_handler)
|
||||
changed = ansible_module.execute_ipa_commands(
|
||||
commands, fail_on_member_errors=True)
|
||||
|
||||
# Done
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ options:
|
||||
aliases: ["fqdn"]
|
||||
required: false
|
||||
hosts:
|
||||
description: The list of user host dicts
|
||||
description: The list of host dicts
|
||||
required: false
|
||||
type: list
|
||||
elements: dict
|
||||
@@ -63,7 +63,7 @@ options:
|
||||
type: str
|
||||
required: false
|
||||
location:
|
||||
description: Host location (e.g. "Lab 2")
|
||||
description: Host physical location hist (e.g. "Lab 2")
|
||||
type: str
|
||||
aliases: ["ns_host_location"]
|
||||
required: false
|
||||
@@ -184,7 +184,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["krbprincipalauthind"]
|
||||
choices: ["radius", "otp", "pkinit", "hardened", ""]
|
||||
choices: ["radius", "otp", "pkinit", "hardened", "idp", ""]
|
||||
required: false
|
||||
requires_pre_auth:
|
||||
description: Pre-authentication is required for the service
|
||||
@@ -356,7 +356,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["krbprincipalauthind"]
|
||||
choices: ["radius", "otp", "pkinit", "hardened", ""]
|
||||
choices: ["radius", "otp", "pkinit", "hardened", "idp", ""]
|
||||
required: false
|
||||
requires_pre_auth:
|
||||
description: Pre-authentication is required for the service
|
||||
@@ -441,6 +441,15 @@ EXAMPLES = """
|
||||
description: Example host
|
||||
force: yes
|
||||
|
||||
# Ensure multiple hosts are present with random passwords
|
||||
- ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
hosts:
|
||||
- name: host01.example.com
|
||||
random: yes
|
||||
- name: host02.example.com
|
||||
random: yes
|
||||
|
||||
# Initiate generation of a random password for the host
|
||||
- ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
@@ -449,6 +458,18 @@ EXAMPLES = """
|
||||
ip_address: 192.168.0.123
|
||||
random: yes
|
||||
|
||||
# Ensure multiple hosts are present with principals
|
||||
- ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
hosts:
|
||||
- name: host01.example.com
|
||||
principal:
|
||||
- host/testhost01.example.com
|
||||
- name: host02.example.com
|
||||
principal:
|
||||
- host/myhost01.example.com
|
||||
action: member
|
||||
|
||||
# Ensure host is disabled
|
||||
- ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
@@ -466,16 +487,18 @@ EXAMPLES = """
|
||||
RETURN = """
|
||||
host:
|
||||
description: Host dict with random password
|
||||
returned: If random is yes and user did not exist or update_password is yes
|
||||
returned: If random is yes and host did not exist or update_password is yes
|
||||
type: dict
|
||||
contains:
|
||||
randompassword:
|
||||
description: The generated random password
|
||||
type: str
|
||||
returned: If only one user is handled by the module
|
||||
returned: |
|
||||
If only one host is handled by the module without using hosts parameter
|
||||
name:
|
||||
description: The user name of the user that got a new random password
|
||||
returned: If several users are handled by the module
|
||||
description: The host name of the host that got a new random password
|
||||
returned: |
|
||||
If several hosts are handled by the module with the hosts parameter
|
||||
type: dict
|
||||
contains:
|
||||
randompassword:
|
||||
@@ -486,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
|
||||
@@ -510,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
|
||||
|
||||
|
||||
@@ -644,53 +674,27 @@ def check_parameters( # pylint: disable=unused-argument
|
||||
module.params_fail_used_invalid(invalid, state, action)
|
||||
|
||||
|
||||
def check_authind(module, auth_ind):
|
||||
_invalid = module.ipa_command_invalid_param_choices(
|
||||
"host_add", "krbprincipalauthind", auth_ind)
|
||||
if _invalid:
|
||||
module.fail_json(
|
||||
msg="The use of krbprincipalauthind '%s' is not supported "
|
||||
"by your IPA version" % "','".join(_invalid))
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def result_handler(module, result, command, name, args, errors, exit_args,
|
||||
one_name):
|
||||
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"]:
|
||||
if one_name:
|
||||
if single_host:
|
||||
exit_args["randompassword"] = \
|
||||
result["result"]["randompassword"]
|
||||
else:
|
||||
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, one_name):
|
||||
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(
|
||||
@@ -753,7 +757,8 @@ def main():
|
||||
default=None),
|
||||
auth_ind=dict(type='list', elements="str",
|
||||
aliases=["krbprincipalauthind"], default=None,
|
||||
choices=['radius', 'otp', 'pkinit', 'hardened', '']),
|
||||
choices=["radius", "otp", "pkinit", "hardened", "idp",
|
||||
""]),
|
||||
requires_pre_auth=dict(type="bool", aliases=["ipakrbrequirespreauth"],
|
||||
default=None),
|
||||
ok_as_delegate=dict(type="bool", aliases=["ipakrbokasdelegate"],
|
||||
@@ -843,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(
|
||||
@@ -882,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
|
||||
@@ -896,6 +908,8 @@ def main():
|
||||
|
||||
# Check version specific settings
|
||||
|
||||
check_authind(ansible_module, auth_ind)
|
||||
|
||||
server_realm = ansible_module.ipa_get_realm()
|
||||
|
||||
commands = []
|
||||
@@ -938,6 +952,7 @@ def main():
|
||||
sshpubkey = host.get("sshpubkey")
|
||||
userclass = host.get("userclass")
|
||||
auth_ind = host.get("auth_ind")
|
||||
check_authind(ansible_module, auth_ind)
|
||||
requires_pre_auth = host.get("requires_pre_auth")
|
||||
ok_as_delegate = host.get("ok_as_delegate")
|
||||
ok_to_auth_as_delegate = host.get("ok_to_auth_as_delegate")
|
||||
@@ -962,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" %
|
||||
@@ -1037,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
|
||||
@@ -1069,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.
|
||||
@@ -1137,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):
|
||||
@@ -1361,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":
|
||||
@@ -1375,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":
|
||||
@@ -1389,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":
|
||||
@@ -1398,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:
|
||||
@@ -1467,8 +1599,8 @@ def main():
|
||||
# Execute commands
|
||||
|
||||
changed = ansible_module.execute_ipa_commands(
|
||||
commands, result_handler, exception_handler,
|
||||
exit_args=exit_args, one_name=len(names) == 1)
|
||||
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(
|
||||
|
||||
341
plugins/modules/ipaidoverridegroup.py
Normal file
341
plugins/modules/ipaidoverridegroup.py
Normal file
@@ -0,0 +1,341 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2023 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, exither 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"],
|
||||
}
|
||||
|
||||
# No rename support: 'ID overrides cannot be renamed'
|
||||
# ipaserver/plugins/idviews.py:baseidoverride_mod:pre_callback
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: ipaidoverridegroup
|
||||
short_description: Manage FreeIPA idoverridegroup
|
||||
description: Manage FreeIPA idoverridegroups
|
||||
extends_documentation_fragment:
|
||||
- ipamodule_base_docs
|
||||
options:
|
||||
idview:
|
||||
description: The idoverridegroup idview string.
|
||||
type: str
|
||||
required: true
|
||||
aliases: ["idviewcn"]
|
||||
anchor:
|
||||
description: The list of anchors to override
|
||||
type: list
|
||||
elements: str
|
||||
required: true
|
||||
aliases: ["ipaanchoruuid"]
|
||||
description:
|
||||
description: Description
|
||||
type: str
|
||||
required: False
|
||||
aliases: ["desc"]
|
||||
name:
|
||||
description: Group name
|
||||
type: str
|
||||
required: False
|
||||
aliases: ["group_name", "cn"]
|
||||
gid:
|
||||
description: Group ID Number (int or "")
|
||||
type: str
|
||||
required: False
|
||||
aliases: ["gidnumber"]
|
||||
fallback_to_ldap:
|
||||
description: |
|
||||
Allow falling back to AD DC LDAP when resolving AD trusted objects.
|
||||
For two-way trusts only.
|
||||
required: False
|
||||
type: bool
|
||||
delete_continue:
|
||||
description: |
|
||||
Continuous mode. Don't stop on errors.
|
||||
Valid only if `state` is `absent`.
|
||||
required: false
|
||||
type: bool
|
||||
aliases: ["continue"]
|
||||
state:
|
||||
description: The state to ensure.
|
||||
choices: ["present", "absent"]
|
||||
default: present
|
||||
type: str
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
# Ensure test group test_group is present in idview test_idview
|
||||
- ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
|
||||
# Ensure test group test_group is present in idview test_idview with
|
||||
# description
|
||||
- ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
description: "test_group description"
|
||||
|
||||
# Ensure test group test_group is present in idview test_idview without
|
||||
# description
|
||||
- ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
description: ""
|
||||
|
||||
# Ensure test group test_group is present in idview test_idview with internal
|
||||
# name test_123_group
|
||||
- ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
name: test_123_group
|
||||
|
||||
# Ensure test group test_group is present in idview test_idview without
|
||||
# internal name
|
||||
- ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
name: ""
|
||||
|
||||
# Ensure test group test_group is present in idview test_idview with gid 20001
|
||||
- ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
gid: 20001
|
||||
|
||||
# Ensure test group test_group is present in idview test_idview without gid
|
||||
- ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
gid: ""
|
||||
|
||||
# Ensure test group test_group is absent in idview test_idview
|
||||
- ipaidoverridegroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_group
|
||||
continue: true
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
"""
|
||||
|
||||
|
||||
from ansible.module_utils.ansible_freeipa_module import \
|
||||
IPAAnsibleModule, compare_args_ipa
|
||||
from ansible.module_utils import six
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
|
||||
def find_idoverridegroup(module, idview, anchor):
|
||||
"""Find if a idoverridegroup with the given name already exist."""
|
||||
try:
|
||||
_result = module.ipa_command("idoverridegroup_show", idview,
|
||||
{"ipaanchoruuid": anchor,
|
||||
"all": True})
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# An exception is raised if idoverridegroup anchor is not found.
|
||||
return None
|
||||
return _result["result"]
|
||||
|
||||
|
||||
def gen_args(anchor, description, name, gid):
|
||||
# fallback_to_ldap is only a runtime tuning parameter
|
||||
_args = {}
|
||||
if anchor is not None:
|
||||
_args["ipaanchoruuid"] = anchor
|
||||
if description is not None:
|
||||
_args["description"] = description
|
||||
if name is not None:
|
||||
_args["cn"] = name
|
||||
if gid is not None:
|
||||
_args["gidnumber"] = gid
|
||||
return _args
|
||||
|
||||
|
||||
def gen_args_runtime(fallback_to_ldap):
|
||||
_args = {}
|
||||
if fallback_to_ldap is not None:
|
||||
_args["fallback_to_ldap"] = fallback_to_ldap
|
||||
return _args
|
||||
|
||||
|
||||
def merge_dicts(dict1, dict2):
|
||||
ret = dict1.copy()
|
||||
ret.update(dict2)
|
||||
return ret
|
||||
|
||||
|
||||
def main():
|
||||
ansible_module = IPAAnsibleModule(
|
||||
argument_spec=dict(
|
||||
# general
|
||||
idview=dict(type="str", required=True, aliases=["idviewcn"]),
|
||||
anchor=dict(type="list", elements="str", required=True,
|
||||
aliases=["ipaanchoruuid"]),
|
||||
|
||||
# present
|
||||
description=dict(type="str", required=False, aliases=["desc"]),
|
||||
name=dict(type="str", required=False,
|
||||
aliases=["group_name", "cn"]),
|
||||
gid=dict(type="str", required=False, aliases=["gidnumber"]),
|
||||
|
||||
# runtime flags
|
||||
fallback_to_ldap=dict(type="bool", required=False),
|
||||
|
||||
# absent
|
||||
delete_continue=dict(type="bool", required=False,
|
||||
aliases=['continue'], default=None),
|
||||
|
||||
# No rename support: 'ID overrides cannot be renamed'
|
||||
# ipaserver/plugins/idviews.py:baseidoverride_mod:pre_callback
|
||||
|
||||
# state
|
||||
state=dict(type="str", default="present",
|
||||
choices=["present", "absent"]),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
|
||||
# Get parameters
|
||||
|
||||
# general
|
||||
idview = ansible_module.params_get("idview")
|
||||
anchors = ansible_module.params_get("anchor")
|
||||
|
||||
# present
|
||||
description = ansible_module.params_get("description")
|
||||
name = ansible_module.params_get("name")
|
||||
gid = ansible_module.params_get_with_type_cast("gid", int)
|
||||
|
||||
# runtime flags
|
||||
fallback_to_ldap = ansible_module.params_get("fallback_to_ldap")
|
||||
|
||||
# absent
|
||||
delete_continue = ansible_module.params_get("delete_continue")
|
||||
|
||||
# state
|
||||
state = ansible_module.params_get("state")
|
||||
|
||||
# Check parameters
|
||||
|
||||
invalid = []
|
||||
|
||||
if state == "present":
|
||||
if len(anchors) != 1:
|
||||
ansible_module.fail_json(
|
||||
msg="Only one idoverridegroup can be added at a time.")
|
||||
invalid = ["delete_continue"]
|
||||
|
||||
if state == "absent":
|
||||
if len(anchors) < 1:
|
||||
ansible_module.fail_json(msg="No name given.")
|
||||
invalid = ["description", "name", "gid"]
|
||||
|
||||
ansible_module.params_fail_used_invalid(invalid, state)
|
||||
|
||||
# Init
|
||||
|
||||
changed = False
|
||||
exit_args = {}
|
||||
|
||||
# Connect to IPA API
|
||||
with ansible_module.ipa_connect():
|
||||
|
||||
runtime_args = gen_args_runtime(fallback_to_ldap)
|
||||
commands = []
|
||||
for anchor in anchors:
|
||||
# Make sure idoverridegroup exists
|
||||
res_find = find_idoverridegroup(ansible_module, idview, anchor)
|
||||
|
||||
# Create command
|
||||
if state == "present":
|
||||
|
||||
# Generate args
|
||||
args = gen_args(anchor, description, name, gid)
|
||||
# fallback_to_ldap is only a runtime tuning parameter
|
||||
all_args = merge_dicts(args, runtime_args)
|
||||
|
||||
# Found the idoverridegroup
|
||||
if res_find is not None:
|
||||
# For idempotency: Remove empty sshpubkey list if
|
||||
# there are no sshpubkey in the found entry.
|
||||
if "ipasshpubkey" in args and \
|
||||
len(args["ipasshpubkey"]) < 1 and \
|
||||
"ipasshpubkey" not in res_find:
|
||||
del args["ipasshpubkey"]
|
||||
# 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([idview, "idoverridegroup_mod",
|
||||
all_args])
|
||||
else:
|
||||
commands.append([idview, "idoverridegroup_add",
|
||||
all_args])
|
||||
|
||||
elif state == "absent":
|
||||
if res_find is not None:
|
||||
commands.append(
|
||||
[idview, "idoverridegroup_del",
|
||||
merge_dicts(
|
||||
{
|
||||
"ipaanchoruuid": anchor,
|
||||
"continue": delete_continue or False
|
||||
},
|
||||
runtime_args
|
||||
)]
|
||||
)
|
||||
|
||||
else:
|
||||
ansible_module.fail_json(msg="Unkown state '%s'" % state)
|
||||
|
||||
# Execute commands
|
||||
|
||||
changed = ansible_module.execute_ipa_commands(commands)
|
||||
|
||||
# Done
|
||||
|
||||
ansible_module.exit_json(changed=changed, **exit_args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
617
plugins/modules/ipaidoverrideuser.py
Normal file
617
plugins/modules/ipaidoverrideuser.py
Normal file
@@ -0,0 +1,617 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2023 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"],
|
||||
}
|
||||
|
||||
# No rename support: 'ID overrides cannot be renamed'
|
||||
# ipaserver/plugins/idviews.py:baseidoverride_mod:pre_callback
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: ipaidoverrideuser
|
||||
short_description: Manage FreeIPA idoverrideuser
|
||||
description: Manage FreeIPA idoverrideuser and idoverrideuser members
|
||||
extends_documentation_fragment:
|
||||
- ipamodule_base_docs
|
||||
options:
|
||||
idview:
|
||||
description: The idoverrideuser idview string.
|
||||
type: str
|
||||
required: true
|
||||
aliases: ["idviewcn"]
|
||||
anchor:
|
||||
description: The list of anchors to override
|
||||
type: list
|
||||
elements: str
|
||||
required: true
|
||||
aliases: ["ipaanchoruuid"]
|
||||
description:
|
||||
description: Description
|
||||
type: str
|
||||
required: False
|
||||
aliases: ["desc"]
|
||||
name:
|
||||
description: The user (internally uid)
|
||||
type: str
|
||||
required: False
|
||||
aliases: ["login"]
|
||||
uid:
|
||||
description: User ID Number (int or "")
|
||||
type: str
|
||||
required: False
|
||||
aliases: ["uidnumber"]
|
||||
gecos:
|
||||
description: GECOS
|
||||
required: False
|
||||
type: str
|
||||
gidnumber:
|
||||
description: Group ID Number (int or "")
|
||||
required: False
|
||||
type: str
|
||||
homedir:
|
||||
description: Home directory
|
||||
type: str
|
||||
required: False
|
||||
aliases: ["homedirectory"]
|
||||
shell:
|
||||
description: Login shell
|
||||
type: str
|
||||
required: False
|
||||
aliases: ["loginshell"]
|
||||
sshpubkey:
|
||||
description: List of SSH public keys
|
||||
type: list
|
||||
elements: str
|
||||
required: False
|
||||
aliases: ["ipasshpubkey"]
|
||||
certificate:
|
||||
description: List of Base-64 encoded user certificates
|
||||
type: list
|
||||
elements: str
|
||||
required: False
|
||||
aliases: ["usercertificate"]
|
||||
fallback_to_ldap:
|
||||
description: |
|
||||
Allow falling back to AD DC LDAP when resolving AD trusted objects.
|
||||
For two-way trusts only.
|
||||
required: False
|
||||
type: bool
|
||||
delete_continue:
|
||||
description: |
|
||||
Continuous mode. Don't stop on errors.
|
||||
Valid only if `state` is `absent`.
|
||||
required: false
|
||||
type: bool
|
||||
aliases: ["continue"]
|
||||
nomembers:
|
||||
description: |
|
||||
Suppress processing of membership attributes.
|
||||
Valid only if `state` is `absent`.
|
||||
type: bool
|
||||
required: False
|
||||
aliases: ["no_members"]
|
||||
action:
|
||||
description: Work on idoverrideuser or member level.
|
||||
choices: ["idoverrideuser", "member"]
|
||||
default: idoverrideuser
|
||||
type: str
|
||||
state:
|
||||
description: The state to ensure.
|
||||
choices: ["present", "absent"]
|
||||
default: present
|
||||
type: str
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
# Ensure test user test_user is present in idview test_idview
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview with description
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
description: "test_user description"
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview without
|
||||
# description
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
description: ""
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview with internal
|
||||
# name test_123_user
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
name: test_123_user
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview without internal
|
||||
# name
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
name: ""
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview with uid 20001
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
uid: 20001
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview without uid
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
uid: ""
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview with gecos
|
||||
# "Gecos Test"
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
gecos: Gecos Test
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview without gecos
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
gecos: ""
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview with gidnumber
|
||||
# 20001
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
gidnumber: 20001
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview without
|
||||
# gidnumber
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
gidnumber: ""
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview with homedir
|
||||
# /Users
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
homedir: /Users
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview without homedir
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
homedir: ""
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview with shell
|
||||
# /bin/someshell
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
shell: /bin/someshell
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview without shell
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
shell: ""
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview with sshpubkey
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
sshpubkey:
|
||||
- ssh-rsa AAAAB3NzaC1yc2EAAADAQABAAABgQCqmVDpEX5gnSjKuv97Ay ...
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview without
|
||||
# sshpubkey
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
sshpubkey: []
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview with 1
|
||||
# certificate
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
certificate:
|
||||
- "{{ lookup('file', 'cert1.b64', rstrip=False) }}"
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview with 3
|
||||
# certificate members
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
certificate:
|
||||
- "{{ lookup('file', 'cert1.b64', rstrip=False) }}"
|
||||
- "{{ lookup('file', 'cert2.b64', rstrip=False) }}"
|
||||
- "{{ lookup('file', 'cert3.b64', rstrip=False) }}"
|
||||
action: member
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview without
|
||||
# 2 certificate members
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
certificate:
|
||||
- "{{ lookup('file', 'cert2.b64', rstrip=False) }}"
|
||||
- "{{ lookup('file', 'cert3.b64', rstrip=False) }}"
|
||||
action: member
|
||||
state: absent
|
||||
|
||||
# Ensure test user test_user is present in idview test_idview without
|
||||
# certificates
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
certificate: []
|
||||
|
||||
# Ensure test user test_user is absent in idview test_idview
|
||||
- ipaidoverrideuser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
idview: test_idview
|
||||
anchor: test_user
|
||||
continue: true
|
||||
state: absent
|
||||
"""
|
||||
|
||||
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, convert_input_certificates
|
||||
from ansible.module_utils import six
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
|
||||
def find_idoverrideuser(module, idview, anchor):
|
||||
"""Find if a idoverrideuser with the given name already exist."""
|
||||
try:
|
||||
_result = module.ipa_command("idoverrideuser_show", idview,
|
||||
{"ipaanchoruuid": anchor,
|
||||
"all": True})
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# An exception is raised if idoverrideuser anchor is not found.
|
||||
return None
|
||||
|
||||
_res = _result["result"]
|
||||
certs = _res.get("usercertificate")
|
||||
if certs is not None:
|
||||
_res["usercertificate"] = [encode_certificate(cert) for cert in certs]
|
||||
return _res
|
||||
|
||||
|
||||
def gen_args(anchor, description, name, uid, gecos, gidnumber, homedir, shell,
|
||||
sshpubkey):
|
||||
# fallback_to_ldap and nomembers are only runtime tuning parameters
|
||||
_args = {}
|
||||
if anchor is not None:
|
||||
_args["ipaanchoruuid"] = anchor
|
||||
if description is not None:
|
||||
_args["description"] = description
|
||||
if name is not None:
|
||||
_args["uid"] = name
|
||||
if uid is not None:
|
||||
_args["uidnumber"] = uid
|
||||
if gecos is not None:
|
||||
_args["gecos"] = gecos
|
||||
if gidnumber is not None:
|
||||
_args["gidnumber"] = gidnumber
|
||||
if homedir is not None:
|
||||
_args["homedirectory"] = homedir
|
||||
if shell is not None:
|
||||
_args["loginshell"] = shell
|
||||
if sshpubkey is not None:
|
||||
_args["ipasshpubkey"] = sshpubkey
|
||||
return _args
|
||||
|
||||
|
||||
def gen_args_runtime(fallback_to_ldap, nomembers):
|
||||
_args = {}
|
||||
if fallback_to_ldap is not None:
|
||||
_args["fallback_to_ldap"] = fallback_to_ldap
|
||||
if nomembers is not None:
|
||||
_args["no_members"] = nomembers
|
||||
return _args
|
||||
|
||||
|
||||
def gen_member_args(certificate):
|
||||
_args = {}
|
||||
if certificate is not None:
|
||||
_args["usercertificate"] = certificate
|
||||
return _args
|
||||
|
||||
|
||||
def merge_dicts(dict1, dict2):
|
||||
ret = dict1.copy()
|
||||
ret.update(dict2)
|
||||
return ret
|
||||
|
||||
|
||||
def main():
|
||||
ansible_module = IPAAnsibleModule(
|
||||
argument_spec=dict(
|
||||
# general
|
||||
idview=dict(type="str", required=True, aliases=["idviewcn"]),
|
||||
anchor=dict(type="list", elements="str", required=True,
|
||||
aliases=["ipaanchoruuid"]),
|
||||
|
||||
# present
|
||||
description=dict(type="str", required=False, aliases=["desc"]),
|
||||
name=dict(type="str", required=False, aliases=["login"]),
|
||||
uid=dict(type="str", required=False, aliases=["uidnumber"]),
|
||||
gecos=dict(type="str", required=False),
|
||||
gidnumber=dict(type="str", required=False),
|
||||
homedir=dict(type="str", required=False,
|
||||
aliases=["homedirectory"]),
|
||||
shell=dict(type="str", required=False, aliases=["loginshell"]),
|
||||
sshpubkey=dict(type="list", elements="str", required=False,
|
||||
aliases=["ipasshpubkey"]),
|
||||
certificate=dict(type="list", elements="str", required=False,
|
||||
aliases=["usercertificate"]),
|
||||
fallback_to_ldap=dict(type="bool", required=False),
|
||||
nomembers=dict(type="bool", required=False,
|
||||
aliases=["no_members"]),
|
||||
|
||||
# absent
|
||||
delete_continue=dict(type="bool", required=False,
|
||||
aliases=['continue'], default=None),
|
||||
|
||||
# No rename support: 'ID overrides cannot be renamed'
|
||||
# ipaserver/plugins/idviews.py:baseidoverride_mod:pre_callback
|
||||
|
||||
# action
|
||||
action=dict(type="str", default="idoverrideuser",
|
||||
choices=["member", "idoverrideuser"]),
|
||||
# state
|
||||
state=dict(type="str", default="present",
|
||||
choices=["present", "absent"]),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
|
||||
# Get parameters
|
||||
|
||||
# general
|
||||
idview = ansible_module.params_get("idview")
|
||||
anchors = ansible_module.params_get("anchor")
|
||||
|
||||
# present
|
||||
description = ansible_module.params_get("description")
|
||||
name = ansible_module.params_get("name")
|
||||
uid = ansible_module.params_get_with_type_cast("uid", int)
|
||||
gecos = ansible_module.params_get("gecos")
|
||||
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")
|
||||
certificate = ansible_module.params_get("certificate")
|
||||
fallback_to_ldap = ansible_module.params_get("fallback_to_ldap")
|
||||
nomembers = ansible_module.params_get("nomembers")
|
||||
action = ansible_module.params_get("action")
|
||||
|
||||
# absent
|
||||
delete_continue = ansible_module.params_get("delete_continue")
|
||||
|
||||
# state
|
||||
state = ansible_module.params_get("state")
|
||||
|
||||
# Check parameters
|
||||
|
||||
invalid = []
|
||||
|
||||
if state == "present":
|
||||
if len(anchors) != 1:
|
||||
ansible_module.fail_json(
|
||||
msg="Only one idoverrideuser can be added at a time.")
|
||||
invalid = ["delete_continue"]
|
||||
if action == "member":
|
||||
invalid += ["description", "name", "uid", "gecos", "gidnumber",
|
||||
"homedir", "shell", "sshpubkey"]
|
||||
|
||||
if state == "absent":
|
||||
if len(anchors) < 1:
|
||||
ansible_module.fail_json(msg="No name given.")
|
||||
invalid = ["description", "name", "uid", "gecos", "gidnumber",
|
||||
"homedir", "shell", "sshpubkey", "nomembers"]
|
||||
if action == "idoverrideuser":
|
||||
invalid += ["certificate"]
|
||||
|
||||
ansible_module.params_fail_used_invalid(invalid, state, action)
|
||||
|
||||
certificate = convert_input_certificates(ansible_module, certificate,
|
||||
state)
|
||||
|
||||
# Init
|
||||
|
||||
changed = False
|
||||
exit_args = {}
|
||||
|
||||
# Connect to IPA API
|
||||
with ansible_module.ipa_connect():
|
||||
|
||||
runtime_args = gen_args_runtime(fallback_to_ldap, nomembers)
|
||||
commands = []
|
||||
for anchor in anchors:
|
||||
# Make sure idoverrideuser exists
|
||||
res_find = find_idoverrideuser(ansible_module, idview, anchor)
|
||||
|
||||
# add/del lists
|
||||
certificate_add, certificate_del = [], []
|
||||
|
||||
# Create command
|
||||
if state == "present":
|
||||
|
||||
# Generate args
|
||||
args = gen_args(anchor, description, name, uid, gecos,
|
||||
gidnumber, homedir, shell, sshpubkey)
|
||||
# fallback_to_ldap and nomembers are only runtime tuning
|
||||
# parameters
|
||||
all_args = merge_dicts(args, runtime_args)
|
||||
|
||||
if action == "idoverrideuser":
|
||||
# Found the idoverrideuser
|
||||
if res_find is not None:
|
||||
# For idempotency: Remove empty sshpubkey list if
|
||||
# there are no sshpubkey in the found entry.
|
||||
if "ipasshpubkey" in args and \
|
||||
len(args["ipasshpubkey"]) < 1 and \
|
||||
"ipasshpubkey" not in res_find:
|
||||
del args["ipasshpubkey"]
|
||||
# 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([idview, "idoverrideuser_mod",
|
||||
all_args])
|
||||
else:
|
||||
commands.append([idview, "idoverrideuser_add",
|
||||
all_args])
|
||||
res_find = {}
|
||||
|
||||
member_args = gen_member_args(certificate)
|
||||
if not compare_args_ipa(ansible_module, member_args,
|
||||
res_find):
|
||||
|
||||
# Generate addition and removal lists
|
||||
certificate_add, certificate_del = gen_add_del_lists(
|
||||
certificate, res_find.get("usercertificate"))
|
||||
|
||||
elif action == "member":
|
||||
if res_find is None:
|
||||
ansible_module.fail_json(
|
||||
msg="No idoverrideuser '%s' in idview '%s'" %
|
||||
(anchor, idview))
|
||||
|
||||
# Reduce add lists for certificate
|
||||
# to new entries only that are not in res_find.
|
||||
if certificate is not None:
|
||||
certificate_add = gen_add_list(
|
||||
certificate, res_find.get("usercertificate"))
|
||||
|
||||
elif state == "absent":
|
||||
if action == "idoverrideuser":
|
||||
if res_find is not None:
|
||||
commands.append(
|
||||
[idview, "idoverrideuser_del",
|
||||
merge_dicts(
|
||||
{
|
||||
"ipaanchoruuid": anchor,
|
||||
"continue": delete_continue or False
|
||||
},
|
||||
runtime_args
|
||||
)]
|
||||
)
|
||||
|
||||
elif action == "member":
|
||||
if res_find is None:
|
||||
ansible_module.fail_json(
|
||||
msg="No idoverrideuser '%s' in idview '%s'" %
|
||||
(anchor, idview))
|
||||
|
||||
# Reduce del lists of member_host and member_hostgroup,
|
||||
# to the entries only that are in res_find.
|
||||
if certificate is not None:
|
||||
certificate_del = gen_intersection_list(
|
||||
certificate, res_find.get("usercertificate"))
|
||||
|
||||
else:
|
||||
ansible_module.fail_json(msg="Unkown state '%s'" % state)
|
||||
|
||||
# Member management
|
||||
|
||||
# Add members
|
||||
if certificate_add:
|
||||
commands.append([idview, "idoverrideuser_add_cert",
|
||||
merge_dicts(
|
||||
{
|
||||
"ipaanchoruuid": anchor,
|
||||
"usercertificate": certificate_add
|
||||
},
|
||||
runtime_args
|
||||
)])
|
||||
|
||||
# Remove members
|
||||
|
||||
if certificate_del:
|
||||
commands.append([idview, "idoverrideuser_remove_cert",
|
||||
merge_dicts(
|
||||
{
|
||||
"ipaanchoruuid": anchor,
|
||||
"usercertificate": certificate_del
|
||||
},
|
||||
runtime_args
|
||||
)])
|
||||
|
||||
# Execute commands
|
||||
|
||||
changed = ansible_module.execute_ipa_commands(commands)
|
||||
|
||||
# Done
|
||||
|
||||
ansible_module.exit_json(changed=changed, **exit_args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user