mirror of
https://github.com/freeipa/ansible-freeipa.git
synced 2026-03-30 15:23:06 +00:00
Compare commits
216 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
e96f92c36f | ||
|
|
683a894876 | ||
|
|
2761c7e8d9 | ||
|
|
7d3921e510 | ||
|
|
6000aac687 | ||
|
|
e8354932b4 | ||
|
|
a3089484b1 | ||
|
|
1469ac6058 | ||
|
|
308d970b6c | ||
|
|
7b470ceb60 | ||
|
|
77f5d8751f | ||
|
|
3292252802 | ||
|
|
414dc06c86 | ||
|
|
d2f9fe6325 | ||
|
|
d7c02d1347 | ||
|
|
cc6a80fa88 | ||
|
|
fe6edbabdb | ||
|
|
434905432d | ||
|
|
9f773ff5ac | ||
|
|
e95bec1803 | ||
|
|
ea709ebc4d | ||
|
|
add89c25ee | ||
|
|
9108065ea7 | ||
|
|
6cac891287 | ||
|
|
fc5fc9d9ef | ||
|
|
670740bdc0 | ||
|
|
529deae407 | ||
|
|
a945862540 | ||
|
|
8240d9beb6 | ||
|
|
6da6110432 | ||
|
|
1d8deb8e2d | ||
|
|
b3856a1e2c | ||
|
|
410682a01d | ||
|
|
ee59ec2142 | ||
|
|
d043a3bdd1 | ||
|
|
5062ac2b09 | ||
|
|
292e2eb60e | ||
|
|
baa7cae8bf | ||
|
|
6b7633976c | ||
|
|
9a32359a5d | ||
|
|
82e176af95 | ||
|
|
2a1ecdbd83 | ||
|
|
f8b5851610 | ||
|
|
b760863847 | ||
|
|
e3bf82d873 | ||
|
|
76ca587d76 | ||
|
|
5c630d6021 | ||
|
|
483d51b418 | ||
|
|
ba353a9b16 | ||
|
|
56560855b4 | ||
|
|
a8d44e2c52 | ||
|
|
b175c78c95 | ||
|
|
198298b2d0 | ||
|
|
d5269c83e6 | ||
|
|
9d47ffc2b9 | ||
|
|
feadbfce95 | ||
|
|
a9257e7f44 | ||
|
|
d204b6d480 | ||
|
|
c645841444 | ||
|
|
f2a0edeb25 | ||
|
|
45baf5c108 | ||
|
|
deec31c3ab | ||
|
|
fea480b348 | ||
|
|
defd1e4e92 | ||
|
|
adc262bcb0 | ||
|
|
72b4b89116 | ||
|
|
473ed03e26 | ||
|
|
d546b4614d | ||
|
|
872537f4de | ||
|
|
d6658347c9 | ||
|
|
062b53a676 | ||
|
|
470d0ddc1b | ||
|
|
2e707a48cb | ||
|
|
971d40c3a9 | ||
|
|
7d89af48b6 | ||
|
|
03ce096fbb | ||
|
|
91edff3b21 | ||
|
|
84c0188023 | ||
|
|
1f91730b17 | ||
|
|
99c7acbe5f | ||
|
|
14706cc49e | ||
|
|
dde5b06b97 | ||
|
|
c7e83685e3 | ||
|
|
882d60515d | ||
|
|
27cbd40182 |
@@ -16,6 +16,11 @@ exclude_paths:
|
||||
kinds:
|
||||
- playbook: '**/tests/**/test_*.yml'
|
||||
- playbook: '**/playbooks/**/*.yml'
|
||||
- playbook: '**/tests/ca-less/install_*_without_ca.yml'
|
||||
- playbook: '**/tests/ca-less/clean_up_certificates.yml'
|
||||
- playbook: '**/tests/external-signed-ca-with-automatic-copy/install-server-with-external-ca-with-automatic-copy.yml'
|
||||
- playbook: '**/tests/external-signed-ca-with-manual-copy/install-server-with-external-ca-with-manual-copy.yml'
|
||||
- playbook: '**/tests/user/create_users_json.yml'
|
||||
- tasks: '**/tasks_*.yml'
|
||||
- tasks: '**/env_*.yml'
|
||||
|
||||
@@ -28,6 +33,9 @@ skip_list:
|
||||
- '305' # Use shell only when shell functionality is required
|
||||
- '306' # risky-shell-pipe
|
||||
- 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
|
||||
|
||||
|
||||
2
.github/workflows/ansible-test.yml
vendored
2
.github/workflows/ansible-test.yml
vendored
@@ -11,7 +11,5 @@ jobs:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install virtualenv using pip
|
||||
run: pip install virtualenv
|
||||
- name: Run ansible-test
|
||||
run: bash tests/sanity/sanity.sh
|
||||
|
||||
27
.github/workflows/docs.yml
vendored
27
.github/workflows/docs.yml
vendored
@@ -4,8 +4,8 @@ on:
|
||||
- push
|
||||
- pull_request
|
||||
jobs:
|
||||
check_docs_29:
|
||||
name: Check Ansible Documentation with Ansible 2.9.
|
||||
check_docs_oldest_supported:
|
||||
name: Check Ansible Documentation with ansible-core 2.13.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
@@ -14,15 +14,15 @@ jobs:
|
||||
- uses: actions/setup-python@v4.3.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install Ansible 2.9
|
||||
- name: Install Ansible 2.13
|
||||
run: |
|
||||
python -m pip install "ansible < 2.10"
|
||||
python -m pip install "ansible-core >=2.13,<2.14"
|
||||
- name: Run ansible-doc-test
|
||||
run: |
|
||||
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
|
||||
|
||||
check_docs_2_11:
|
||||
name: Check Ansible Documentation with ansible-core 2.11.
|
||||
check_docs_previous:
|
||||
name: Check Ansible Documentation with ansible-core 2.14.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
@@ -31,15 +31,15 @@ jobs:
|
||||
- uses: actions/setup-python@v4.3.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install Ansible 2.11
|
||||
- name: Install Ansible 2.14
|
||||
run: |
|
||||
python -m pip install "ansible-core >=2.11,<2.12"
|
||||
python -m pip install "ansible-core >=2.14,<2.15"
|
||||
- name: Run ansible-doc-test
|
||||
run: |
|
||||
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
|
||||
|
||||
check_docs_2_12:
|
||||
name: Check Ansible Documentation with ansible-core 2.12.
|
||||
check_docs_current:
|
||||
name: Check Ansible Documentation with ansible-core 2.15.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
@@ -48,15 +48,14 @@ jobs:
|
||||
- uses: actions/setup-python@v4.3.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install Ansible 2.12
|
||||
- name: Install Ansible 2.15
|
||||
run: |
|
||||
python -m pip install "ansible-core >=2.12,<2.13"
|
||||
python -m pip install "ansible-core >=2.15,<2.16"
|
||||
- name: Run ansible-doc-test
|
||||
run: |
|
||||
python -m pip install "ansible-core >=2.12,<2.13"
|
||||
ANSIBLE_LIBRARY="." ANSIBLE_DOC_FRAGMENT_PLUGINS="." python utils/ansible-doc-test -v roles plugins
|
||||
|
||||
check_docs_latest:
|
||||
check_docs_ansible_latest:
|
||||
name: Check Ansible Documentation with latest Ansible version.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
16
.github/workflows/lint.yml
vendored
16
.github/workflows/lint.yml
vendored
@@ -16,12 +16,10 @@ jobs:
|
||||
python-version: "3.x"
|
||||
- name: Run ansible-lint
|
||||
run: |
|
||||
pip install ansible-core==2.11.6 ansible-lint
|
||||
find playbooks roles tests -name '*.yml' ! -name "env_*" ! -name "tasks_*" -exec ansible-lint --force-color {} \+
|
||||
env:
|
||||
ANSIBLE_MODULE_UTILS: plugins/module_utils
|
||||
ANSIBLE_LIBRARY: plugins/modules
|
||||
ANSIBLE_DOC_FRAGMENT_PLUGINS: plugins/doc_fragments
|
||||
pip install "ansible-core >=2.15,<2.16" ansible-lint
|
||||
utils/build-galaxy-release.sh -ki
|
||||
cd .galaxy-build
|
||||
ansible-lint
|
||||
|
||||
yamllint:
|
||||
name: Verify yamllint
|
||||
@@ -34,7 +32,7 @@ jobs:
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Run yaml-lint
|
||||
uses: ibiqlik/action-yamllint@v1
|
||||
uses: ibiqlik/action-yamllint@v3.1.1
|
||||
|
||||
pydocstyle:
|
||||
name: Verify pydocstyle
|
||||
@@ -63,7 +61,7 @@ jobs:
|
||||
python-version: "3.x"
|
||||
- name: Run flake8
|
||||
run: |
|
||||
pip install flake8
|
||||
pip install flake8 flake8-bugbear
|
||||
flake8
|
||||
|
||||
pylint:
|
||||
@@ -78,7 +76,7 @@ jobs:
|
||||
python-version: "3.x"
|
||||
- name: Run pylint
|
||||
run: |
|
||||
pip install pylint==2.13.7 wrapt==1.14.0
|
||||
pip install pylint==2.14.4 wrapt==1.14.0
|
||||
pylint plugins roles --disable=import-error
|
||||
|
||||
shellcheck:
|
||||
|
||||
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,7 +1,7 @@
|
||||
---
|
||||
repos:
|
||||
- repo: https://github.com/ansible/ansible-lint.git
|
||||
rev: v5.3.2
|
||||
rev: v6.6.1
|
||||
hooks:
|
||||
- id: ansible-lint
|
||||
always_run: false
|
||||
@@ -11,20 +11,20 @@ repos:
|
||||
entry: |
|
||||
env ANSIBLE_LIBRARY=./plugins/modules ANSIBLE_MODULE_UTILS=./plugins/module_utils ANSIBLE_DOC_FRAGMENT_PLUGINS=./plugins/doc_fragments ansible-lint
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
rev: v1.26.1
|
||||
rev: v1.28.0
|
||||
hooks:
|
||||
- id: yamllint
|
||||
files: \.(yaml|yml)$
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 3.9.2
|
||||
rev: 5.0.3
|
||||
hooks:
|
||||
- id: flake8
|
||||
- repo: https://github.com/pycqa/pydocstyle
|
||||
rev: 6.1.1
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: pydocstyle
|
||||
- repo: https://github.com/pycqa/pylint
|
||||
rev: v2.12.2
|
||||
rev: v2.14.4
|
||||
hooks:
|
||||
- id: pylint
|
||||
args:
|
||||
|
||||
@@ -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
|
||||
|
||||
175
README-cert.md
Normal file
175
README-cert.md
Normal file
@@ -0,0 +1,175 @@
|
||||
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.8+
|
||||
* 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
|
||||
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
|
||||
@@ -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
|
||||
|
||||
105
README-group.md
105
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
|
||||
--------
|
||||
@@ -71,6 +74,62 @@ 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 add users to a group:
|
||||
|
||||
```yaml
|
||||
@@ -112,11 +171,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 +186,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 +213,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 +245,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -294,7 +294,7 @@ Variable | Description | Required
|
||||
`name` \| `service` | The list of service name strings. | yes
|
||||
`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
|
||||
|
||||
@@ -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"
|
||||
@@ -352,6 +353,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
|
||||
=========
|
||||
@@ -393,8 +421,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 +437,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 +445,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,6 +454,10 @@ 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
|
||||
`nomembers` | Suppress processing of membership attributes. (bool) | no
|
||||
|
||||
|
||||
@@ -434,11 +470,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
|
||||
Rafael Jeffman
|
||||
|
||||
@@ -17,6 +17,7 @@ Features
|
||||
* 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
|
||||
@@ -436,6 +437,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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
become: no
|
||||
|
||||
tasks:
|
||||
- name: ensure map TestMap is absent
|
||||
- name: Ensure map TestMap is absent
|
||||
ipaautomountmap:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: TestMap
|
||||
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
|
||||
@@ -4,7 +4,7 @@
|
||||
become: no
|
||||
|
||||
tasks:
|
||||
- name: ensure map TestMap is present
|
||||
- name: Ensure map TestMap is present
|
||||
ipaautomountmap:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: TestMap
|
||||
14
playbooks/cert/cert-hold.yml
Normal file
14
playbooks/cert/cert-hold.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
- 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
|
||||
@@ -11,5 +11,5 @@
|
||||
register: serverconfig
|
||||
|
||||
- name: Display current configuration.
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ serverconfig }}"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
gather_facts: no
|
||||
|
||||
tasks:
|
||||
- name: set ca_renewal_master_server
|
||||
- name: Set ca_renewal_master_server
|
||||
ipaconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ca_renewal_master_server: carenewal.example.com
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
- name: dnszone present
|
||||
- name: All dnszone parameters
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
- name: dnszone present
|
||||
- name: Dnszone present
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
|
||||
@@ -11,5 +11,5 @@
|
||||
register: result
|
||||
|
||||
- name: Zone name inferred from `name_from_ip`
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
msg: "Zone created: {{ result.dnszone.name }}"
|
||||
|
||||
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
|
||||
@@ -14,5 +14,5 @@
|
||||
register: ipahost
|
||||
|
||||
- name: Print generated random password
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
var: ipahost.host.randompassword
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
register: ipahost
|
||||
|
||||
- name: Print generated random password
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
var: ipahost.host.randompassword
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
register: ipahost
|
||||
|
||||
- name: Print generated random password for host01.example.com
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
var: ipahost.host["host01.example.com"].randompassword
|
||||
|
||||
- name: Print generated random password for host02.example.com
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
var: ipahost.host["host02.example.com"].randompassword
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: ensure the trust is present
|
||||
- name: Ensure the trust is present
|
||||
ipatrust:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
realm: windows.local
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: ensure the trust is absent
|
||||
- name: Ensure the trust is absent
|
||||
ipatrust:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
realm: windows.local
|
||||
|
||||
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
|
||||
@@ -15,5 +15,5 @@
|
||||
register: ipauser
|
||||
|
||||
- name: Print generated random password
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
var: ipauser.user.randompassword
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
register: ipauser
|
||||
|
||||
- name: Print generated random password for user1
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
var: ipauser.user.user1.randompassword
|
||||
|
||||
- name: Print generated random password for user2
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
var: ipauser.user.user2.randompassword
|
||||
|
||||
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:"
|
||||
@@ -15,5 +15,5 @@
|
||||
register: result
|
||||
no_log: true
|
||||
- name: Display retrieved data.
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
msg: "Data: {{ result.vault.data }}"
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
register: result
|
||||
no_log: true
|
||||
- name: Display retrieved data.
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
msg: "Data: {{ result.vault.data }}"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
tasks:
|
||||
- name: Copy file containing password to server.
|
||||
copy:
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/password.txt"
|
||||
dest: "{{ ansible_facts['env'].HOME }}/password.txt"
|
||||
owner: "{{ ansible_user }}"
|
||||
@@ -20,6 +20,6 @@
|
||||
vault_type: symmetric
|
||||
vault_password_file: "{{ ansible_facts['env'].HOME }}/password.txt"
|
||||
- name: Remove file containing password from server.
|
||||
file:
|
||||
ansible.builtin.file:
|
||||
path: "{{ ansible_facts['env'].HOME }}/password.txt"
|
||||
state: absent
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
tasks:
|
||||
- name: Copy public key file to server.
|
||||
copy:
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/public.pem"
|
||||
dest: "{{ ansible_facts['env'].HOME }}/public.pem"
|
||||
owner: "{{ ansible_user }}"
|
||||
@@ -25,6 +25,6 @@
|
||||
vault_type: asymmetric
|
||||
vault_public_key_file: "{{ ansible_facts['env'].HOME }}/public.pem"
|
||||
- name: Remove public key file from server.
|
||||
file:
|
||||
ansible.builtin.file:
|
||||
path: "{{ ansible_facts['env'].HOME }}/public.pem"
|
||||
state: absent
|
||||
|
||||
@@ -29,7 +29,8 @@ __all__ = ["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"]
|
||||
|
||||
import os
|
||||
# ansible-freeipa requires locale to be C, IPA requires utf-8.
|
||||
@@ -41,6 +42,7 @@ import tempfile
|
||||
import shutil
|
||||
import socket
|
||||
import base64
|
||||
import ast
|
||||
from datetime import datetime
|
||||
from contextlib import contextmanager
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
@@ -48,6 +50,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+.
|
||||
@@ -106,6 +109,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:
|
||||
@@ -747,8 +751,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():
|
||||
@@ -1125,8 +1129,8 @@ class IPAAnsibleModule(AnsibleModule):
|
||||
def ipa_get_domain(self):
|
||||
"""Retrieve IPA API domain."""
|
||||
if not hasattr(self, "__ipa_api_domain"):
|
||||
setattr(self, "__ipa_api_domain", api_get_domain())
|
||||
return getattr(self, "__ipa_api_domain")
|
||||
setattr(self, "__ipa_api_domain", api_get_domain()) # noqa: B010
|
||||
return getattr(self, "__ipa_api_domain") # noqa: B009
|
||||
|
||||
@staticmethod
|
||||
def ipa_get_realm():
|
||||
@@ -1166,6 +1170,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):
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
@@ -110,6 +129,35 @@ class AutomountMap(IPAAnsibleModule):
|
||||
else:
|
||||
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 = []
|
||||
name = self.params_get("name")
|
||||
@@ -118,15 +166,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): # pylint: disable=no-self-use
|
||||
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 +194,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 +206,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 +323,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
|
||||
|
||||
571
plugins/modules/ipacert.py
Normal file
571
plugins/modules/ipacert.py
Normal file
@@ -0,0 +1,571 @@
|
||||
#!/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
|
||||
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:
|
||||
authors:
|
||||
- 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.
|
||||
@@ -425,6 +426,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),
|
||||
@@ -525,6 +527,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)
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -280,7 +280,8 @@ class DNSZoneModule(IPAAnsibleModule):
|
||||
if any(invalid_ips):
|
||||
self.fail_json(msg=error_msg % invalid_ips)
|
||||
|
||||
def is_valid_nsec3param_rec(self, nsec3param_rec): # pylint: disable=R0201
|
||||
@staticmethod
|
||||
def is_valid_nsec3param_rec(nsec3param_rec):
|
||||
try:
|
||||
part1, part2, part3, part4 = nsec3param_rec.split(" ")
|
||||
except ValueError:
|
||||
|
||||
@@ -41,8 +41,88 @@ 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
|
||||
description:
|
||||
description: The group description
|
||||
type: str
|
||||
@@ -144,6 +224,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 +248,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 +256,17 @@ 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
|
||||
|
||||
# Create a non-POSIX group
|
||||
- ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
@@ -189,7 +288,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 +311,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 +379,22 @@ def gen_member_args(user, group, service, externalmember, idoverrideuser):
|
||||
return _args
|
||||
|
||||
|
||||
def check_parameters(module, state, action):
|
||||
invalid = []
|
||||
if state == "present":
|
||||
if action == "member":
|
||||
invalid = ["description", "gid", "posix", "nonposix", "external",
|
||||
"nomembers"]
|
||||
|
||||
else:
|
||||
invalid = ["description", "gid", "posix", "nonposix", "external",
|
||||
"nomembers"]
|
||||
if action == "group":
|
||||
invalid.extend(["user", "group", "service", "externalmember"])
|
||||
|
||||
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 +423,63 @@ 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"
|
||||
])
|
||||
)
|
||||
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"]),
|
||||
|
||||
# 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 +489,7 @@ def main():
|
||||
|
||||
# general
|
||||
names = ansible_module.params_get("name")
|
||||
groups = ansible_module.params_get("groups")
|
||||
|
||||
# present
|
||||
description = ansible_module.params_get("description")
|
||||
@@ -354,31 +511,50 @@ def main():
|
||||
state = ansible_module.params_get("state")
|
||||
|
||||
# Check parameters
|
||||
invalid = []
|
||||
|
||||
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 == "present":
|
||||
if len(names) != 1:
|
||||
if names is not None and len(names) != 1:
|
||||
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 added at a time using 'name'.")
|
||||
|
||||
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 +565,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 +591,57 @@ 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")
|
||||
|
||||
check_parameters(ansible_module, state, action)
|
||||
|
||||
elif isinstance(group_name, (str, unicode)):
|
||||
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)
|
||||
|
||||
@@ -593,10 +818,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 "
|
||||
|
||||
@@ -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:
|
||||
@@ -644,12 +667,21 @@ 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):
|
||||
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:
|
||||
@@ -671,7 +703,7 @@ def result_handler(module, result, command, name, args, errors, exit_args,
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def exception_handler(module, ex, errors, exit_args, one_name):
|
||||
def exception_handler(module, ex, errors, exit_args, single_host):
|
||||
msg = str(ex)
|
||||
if "already contains" in msg \
|
||||
or "does not contain" in msg:
|
||||
@@ -753,7 +785,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"],
|
||||
@@ -896,6 +929,8 @@ def main():
|
||||
|
||||
# Check version specific settings
|
||||
|
||||
check_authind(ansible_module, auth_ind)
|
||||
|
||||
server_realm = ansible_module.ipa_get_realm()
|
||||
|
||||
commands = []
|
||||
@@ -938,6 +973,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")
|
||||
@@ -1468,7 +1504,7 @@ def main():
|
||||
|
||||
changed = ansible_module.execute_ipa_commands(
|
||||
commands, result_handler, exception_handler,
|
||||
exit_args=exit_args, one_name=len(names) == 1)
|
||||
exit_args=exit_args, single_host=hosts is None)
|
||||
|
||||
# Done
|
||||
|
||||
|
||||
@@ -93,10 +93,12 @@ options:
|
||||
action:
|
||||
description: Work on netgroup or member level
|
||||
required: false
|
||||
type: str
|
||||
default: netgroup
|
||||
choices: ["member", "netgroup"]
|
||||
state:
|
||||
description: The state to ensure.
|
||||
type: str
|
||||
choices: ["present", "absent"]
|
||||
default: present
|
||||
author:
|
||||
@@ -157,18 +159,29 @@ RETURN = """
|
||||
|
||||
from ansible.module_utils.ansible_freeipa_module import \
|
||||
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, \
|
||||
gen_add_list, gen_intersection_list, ipalib_errors, ensure_fqdn
|
||||
gen_add_list, gen_intersection_list, ensure_fqdn
|
||||
|
||||
|
||||
def find_netgroup(module, name):
|
||||
"""Find if a netgroup with the given name already exist."""
|
||||
try:
|
||||
_result = module.ipa_command("netgroup_show", name, {"all": True})
|
||||
except ipalib_errors.NotFound:
|
||||
# An exception is raised if netgroup name is not found.
|
||||
return None
|
||||
else:
|
||||
return _result["result"]
|
||||
_args = {
|
||||
"all": True,
|
||||
"cn": name,
|
||||
}
|
||||
|
||||
# `netgroup_find` is used here instead of `netgroup_show` to workaround
|
||||
# FreeIPA bug https://pagure.io/freeipa/issue/9284.
|
||||
# `ipa netgroup-show hostgroup` shows hostgroup - it's a bug.
|
||||
# `ipa netgroup-find hostgroup` doesn't show hostgroup - it's correct.
|
||||
_result = module.ipa_command("netgroup_find", name, _args)
|
||||
|
||||
if len(_result["result"]) > 1:
|
||||
module.fail_json(
|
||||
msg="There is more than one netgroup '%s'" % name)
|
||||
elif len(_result["result"]) == 1:
|
||||
return _result["result"][0]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def gen_args(description, nisdomain, nomembers):
|
||||
|
||||
@@ -45,83 +45,85 @@ options:
|
||||
required: false
|
||||
aliases: ["cn"]
|
||||
maxlife:
|
||||
description: Maximum password lifetime (in days)
|
||||
type: int
|
||||
description: Maximum password lifetime (in days). (int or "")
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["krbmaxpwdlife"]
|
||||
minlife:
|
||||
description: Minimum password lifetime (in hours)
|
||||
type: int
|
||||
description: Minimum password lifetime (in hours). (int or "")
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["krbminpwdlife"]
|
||||
history:
|
||||
description: Password history size
|
||||
type: int
|
||||
description: Password history size. (int or "")
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["krbpwdhistorylength"]
|
||||
minclasses:
|
||||
description: Minimum number of character classes
|
||||
type: int
|
||||
description: Minimum number of character classes. (int or "")
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["krbpwdmindiffchars"]
|
||||
minlength:
|
||||
description: Minimum length of password
|
||||
type: int
|
||||
description: Minimum length of password. (int or "")
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["krbpwdminlength"]
|
||||
priority:
|
||||
description: Priority of the policy (higher number means lower priority)
|
||||
type: int
|
||||
description: >
|
||||
Priority of the policy (higher number means lower priority). (int or "")
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["cospriority"]
|
||||
maxfail:
|
||||
description: Consecutive failures before lockout
|
||||
type: int
|
||||
description: Consecutive failures before lockout. (int or "")
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["krbpwdmaxfailure"]
|
||||
failinterval:
|
||||
description: Period after which failure count will be reset (seconds)
|
||||
type: int
|
||||
description: >
|
||||
Period after which failure count will be reset (seconds). (int or "")
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["krbpwdfailurecountinterval"]
|
||||
lockouttime:
|
||||
description: Period for which lockout is enforced (seconds)
|
||||
type: int
|
||||
description: Period for which lockout is enforced (seconds). (int or "")
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["krbpwdlockoutduration"]
|
||||
maxrepeat:
|
||||
description: >
|
||||
Maximum number of same consecutive characters.
|
||||
Requires IPA 4.9+
|
||||
type: int
|
||||
Requires IPA 4.9+. (int or "")
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["ipapwdmaxrepeat"]
|
||||
maxsequence:
|
||||
description: >
|
||||
The maximum length of monotonic character sequences (abcd).
|
||||
Requires IPA 4.9+
|
||||
type: int
|
||||
Requires IPA 4.9+. (int or "")
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["ipapwdmaxsequence"]
|
||||
dictcheck:
|
||||
description: >
|
||||
Check if the password is a dictionary word.
|
||||
Requires IPA 4.9+
|
||||
type: bool
|
||||
Requires IPA 4.9+. (bool or "")
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["ipapwdictcheck"]
|
||||
usercheck:
|
||||
description: >
|
||||
Check if the password contains the username.
|
||||
Requires IPA 4.9+
|
||||
type: bool
|
||||
Requires IPA 4.9+. (bool or "")
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["ipapwdusercheck"]
|
||||
gracelimit:
|
||||
description: >
|
||||
Number of LDAP authentications allowed after expiration.
|
||||
Requires IPA 4.10.1+
|
||||
type: int
|
||||
Requires IPA 4.10.1+. (int or "")
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["passwordgracelimit"]
|
||||
state:
|
||||
@@ -151,7 +153,7 @@ RETURN = """
|
||||
"""
|
||||
|
||||
from ansible.module_utils.ansible_freeipa_module import \
|
||||
IPAAnsibleModule, compare_args_ipa
|
||||
IPAAnsibleModule, compare_args_ipa, boolean
|
||||
|
||||
|
||||
def find_pwpolicy(module, name):
|
||||
@@ -171,7 +173,8 @@ def find_pwpolicy(module, name):
|
||||
return None
|
||||
|
||||
|
||||
def gen_args(maxlife, minlife, history, minclasses, minlength, priority,
|
||||
def gen_args(module,
|
||||
maxlife, minlife, history, minclasses, minlength, priority,
|
||||
maxfail, failinterval, lockouttime, maxrepeat, maxsequence,
|
||||
dictcheck, usercheck, gracelimit):
|
||||
_args = {}
|
||||
@@ -196,11 +199,21 @@ def gen_args(maxlife, minlife, history, minclasses, minlength, priority,
|
||||
if maxrepeat is not None:
|
||||
_args["ipapwdmaxrepeat"] = maxrepeat
|
||||
if maxsequence is not None:
|
||||
_args["ipapwdmaxrsequence"] = maxsequence
|
||||
_args["ipapwdmaxsequence"] = maxsequence
|
||||
if dictcheck is not None:
|
||||
_args["ipapwddictcheck"] = dictcheck
|
||||
if module.ipa_check_version("<", "4.9.10"):
|
||||
# Allowed values: "TRUE", "FALSE", ""
|
||||
_args["ipapwddictcheck"] = "TRUE" if dictcheck is True else \
|
||||
"FALSE" if dictcheck is False else dictcheck
|
||||
else:
|
||||
_args["ipapwddictcheck"] = dictcheck
|
||||
if usercheck is not None:
|
||||
_args["ipapwdusercheck"] = usercheck
|
||||
if module.ipa_check_version("<", "4.9.10"):
|
||||
# Allowed values: "TRUE", "FALSE", ""
|
||||
_args["ipapwdusercheck"] = "TRUE" if usercheck is True else \
|
||||
"FALSE" if usercheck is False else usercheck
|
||||
else:
|
||||
_args["ipapwdusercheck"] = usercheck
|
||||
if gracelimit is not None:
|
||||
_args["passwordgracelimit"] = gracelimit
|
||||
|
||||
@@ -219,17 +232,15 @@ def check_supported_params(
|
||||
"pwpolicy_add", "passwordgracelimit")
|
||||
|
||||
# If needed, report unsupported password checking paramteres
|
||||
if not has_password_check:
|
||||
check_password_params = [maxrepeat, maxsequence, dictcheck, usercheck]
|
||||
unsupported = [
|
||||
x for x in check_password_params if x is not None
|
||||
]
|
||||
if unsupported:
|
||||
module.fail_json(
|
||||
msg="Your IPA version does not support arguments: "
|
||||
"maxrepeat, maxsequence, dictcheck, usercheck.")
|
||||
if (
|
||||
not has_password_check
|
||||
and any([maxrepeat, maxsequence, dictcheck, usercheck])
|
||||
):
|
||||
module.fail_json(
|
||||
msg="Your IPA version does not support arguments: "
|
||||
"maxrepeat, maxsequence, dictcheck, usercheck.")
|
||||
|
||||
if gracelimit is not None and not has_gracelimit:
|
||||
if not has_gracelimit and gracelimit is not None:
|
||||
module.fail_json(
|
||||
msg="Your IPA version does not support 'gracelimit'.")
|
||||
|
||||
@@ -242,31 +253,31 @@ def main():
|
||||
default=None, required=False),
|
||||
# present
|
||||
|
||||
maxlife=dict(type="int", aliases=["krbmaxpwdlife"], default=None),
|
||||
minlife=dict(type="int", aliases=["krbminpwdlife"], default=None),
|
||||
history=dict(type="int", aliases=["krbpwdhistorylength"],
|
||||
maxlife=dict(type="str", aliases=["krbmaxpwdlife"], default=None),
|
||||
minlife=dict(type="str", aliases=["krbminpwdlife"], default=None),
|
||||
history=dict(type="str", aliases=["krbpwdhistorylength"],
|
||||
default=None),
|
||||
minclasses=dict(type="int", aliases=["krbpwdmindiffchars"],
|
||||
minclasses=dict(type="str", aliases=["krbpwdmindiffchars"],
|
||||
default=None),
|
||||
minlength=dict(type="int", aliases=["krbpwdminlength"],
|
||||
minlength=dict(type="str", aliases=["krbpwdminlength"],
|
||||
default=None),
|
||||
priority=dict(type="int", aliases=["cospriority"], default=None),
|
||||
maxfail=dict(type="int", aliases=["krbpwdmaxfailure"],
|
||||
priority=dict(type="str", aliases=["cospriority"], default=None),
|
||||
maxfail=dict(type="str", aliases=["krbpwdmaxfailure"],
|
||||
default=None),
|
||||
failinterval=dict(type="int",
|
||||
failinterval=dict(type="str",
|
||||
aliases=["krbpwdfailurecountinterval"],
|
||||
default=None),
|
||||
lockouttime=dict(type="int", aliases=["krbpwdlockoutduration"],
|
||||
lockouttime=dict(type="str", aliases=["krbpwdlockoutduration"],
|
||||
default=None),
|
||||
maxrepeat=dict(type="int", aliases=["ipapwdmaxrepeat"],
|
||||
maxrepeat=dict(type="str", aliases=["ipapwdmaxrepeat"],
|
||||
default=None),
|
||||
maxsequence=dict(type="int", aliases=["ipapwdmaxsequence"],
|
||||
maxsequence=dict(type="str", aliases=["ipapwdmaxsequence"],
|
||||
default=None),
|
||||
dictcheck=dict(type="bool", aliases=["ipapwdictcheck"],
|
||||
dictcheck=dict(type="str", aliases=["ipapwdictcheck"],
|
||||
default=None),
|
||||
usercheck=dict(type="bool", aliases=["ipapwusercheck"],
|
||||
usercheck=dict(type="str", aliases=["ipapwdusercheck"],
|
||||
default=None),
|
||||
gracelimit=dict(type="int", aliases=["passwordgracelimit"],
|
||||
gracelimit=dict(type="str", aliases=["passwordgracelimit"],
|
||||
default=None),
|
||||
# state
|
||||
state=dict(type="str", default="present",
|
||||
@@ -325,7 +336,43 @@ def main():
|
||||
|
||||
ansible_module.params_fail_used_invalid(invalid, state)
|
||||
|
||||
if gracelimit is not None:
|
||||
# Ensure parameter values are valid and have proper type.
|
||||
def int_or_empty_param(value, param):
|
||||
if value is not None and value != "":
|
||||
try:
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
ansible_module.fail_json(
|
||||
msg="Invalid value '%s' for argument '%s'" % (value, param)
|
||||
)
|
||||
return value
|
||||
|
||||
maxlife = int_or_empty_param(maxlife, "maxlife")
|
||||
minlife = int_or_empty_param(minlife, "minlife")
|
||||
history = int_or_empty_param(history, "history")
|
||||
minclasses = int_or_empty_param(minclasses, "minclasses")
|
||||
minlength = int_or_empty_param(minlength, "minlength")
|
||||
priority = int_or_empty_param(priority, "priority")
|
||||
maxfail = int_or_empty_param(maxfail, "maxfail")
|
||||
failinterval = int_or_empty_param(failinterval, "failinterval")
|
||||
lockouttime = int_or_empty_param(lockouttime, "lockouttime")
|
||||
maxrepeat = int_or_empty_param(maxrepeat, "maxrepeat")
|
||||
maxsequence = int_or_empty_param(maxsequence, "maxsequence")
|
||||
gracelimit = int_or_empty_param(gracelimit, "gracelimit")
|
||||
|
||||
def bool_or_empty_param(value, param): # pylint: disable=R1710
|
||||
if value is None or value == "":
|
||||
return value
|
||||
try:
|
||||
return boolean(value)
|
||||
except TypeError as terr:
|
||||
ansible_module.fail_json(msg="Param '%s': %s" % (param, str(terr)))
|
||||
|
||||
dictcheck = bool_or_empty_param(dictcheck, "dictcheck")
|
||||
usercheck = bool_or_empty_param(usercheck, "usercheck")
|
||||
|
||||
# Ensure gracelimit has proper limit.
|
||||
if gracelimit:
|
||||
if gracelimit < -1:
|
||||
ansible_module.fail_json(
|
||||
msg="'gracelimit' must be no less than -1")
|
||||
@@ -351,7 +398,8 @@ def main():
|
||||
# Create command
|
||||
if state == "present":
|
||||
# Generate args
|
||||
args = gen_args(maxlife, minlife, history, minclasses,
|
||||
args = gen_args(ansible_module,
|
||||
maxlife, minlife, history, minclasses,
|
||||
minlength, priority, maxfail, failinterval,
|
||||
lockouttime, maxrepeat, maxsequence, dictcheck,
|
||||
usercheck, gracelimit)
|
||||
|
||||
@@ -45,9 +45,9 @@ options:
|
||||
aliases: ["cn"]
|
||||
location:
|
||||
description: |
|
||||
The server location string.
|
||||
"" for location reset.
|
||||
Only in state: present.
|
||||
The server DNS location.
|
||||
Only available with 'state: present'.
|
||||
Use "" for location reset.
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["ipalocation_location"]
|
||||
@@ -55,46 +55,46 @@ options:
|
||||
description: |
|
||||
Weight for server services
|
||||
Values 0 to 65535, -1 for weight reset.
|
||||
Only in state: present.
|
||||
Only available with 'state: present'.
|
||||
required: false
|
||||
type: int
|
||||
aliases: ["ipaserviceweight"]
|
||||
hidden:
|
||||
description: |
|
||||
Set hidden state of a server.
|
||||
Only in state: present.
|
||||
Only available with 'state: present'.
|
||||
required: false
|
||||
type: bool
|
||||
no_members:
|
||||
description: |
|
||||
Suppress processing of membership attributes
|
||||
Only in state: present.
|
||||
Only available with 'state: present'.
|
||||
required: false
|
||||
type: bool
|
||||
delete_continue:
|
||||
description: |
|
||||
Continuous mode: Don't stop on errors.
|
||||
Only in state: absent.
|
||||
Only available with 'state: absent'.
|
||||
required: false
|
||||
type: bool
|
||||
aliases: ["continue"]
|
||||
ignore_last_of_role:
|
||||
description: |
|
||||
Skip a check whether the last CA master or DNS server is removed.
|
||||
Only in state: absent.
|
||||
Only available with 'state: absent'.
|
||||
required: false
|
||||
type: bool
|
||||
ignore_topology_disconnect:
|
||||
description: |
|
||||
Ignore topology connectivity problems after removal.
|
||||
Only in state: absent.
|
||||
Only available with 'state: absent'.
|
||||
required: false
|
||||
type: bool
|
||||
force:
|
||||
description: |
|
||||
Force server removal even if it does not exist.
|
||||
Will always result in changed.
|
||||
Only in state: absent.
|
||||
Only available with 'state: absent'.
|
||||
required: false
|
||||
type: bool
|
||||
state:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Denis Karpelevich <dkarpele@redhat.com>
|
||||
# Rafael Guterres Jeffman <rjeffman@redhat.com>
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
@@ -45,6 +46,127 @@ options:
|
||||
elements: str
|
||||
required: true
|
||||
aliases: ["service"]
|
||||
services:
|
||||
description: The list of service dicts.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
name:
|
||||
description: The service to manage
|
||||
type: str
|
||||
required: true
|
||||
aliases: ["service"]
|
||||
certificate:
|
||||
description: Base-64 encoded service certificate.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["usercertificate"]
|
||||
pac_type:
|
||||
description: Supported PAC type.
|
||||
required: false
|
||||
choices: ["MS-PAC", "PAD", "NONE", ""]
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["pac_type", "ipakrbauthzdata"]
|
||||
auth_ind:
|
||||
description: Defines an allow list for Authentication Indicators.
|
||||
type: list
|
||||
elements: str
|
||||
required: false
|
||||
choices: ["otp", "radius", "pkinit", "hardened", "idp", ""]
|
||||
aliases: ["krbprincipalauthind"]
|
||||
skip_host_check:
|
||||
description: Skip checking if host object exists.
|
||||
required: False
|
||||
type: bool
|
||||
force:
|
||||
description: Force principal name even if host is not in DNS.
|
||||
required: False
|
||||
type: bool
|
||||
requires_pre_auth:
|
||||
description: Pre-authentication is required for the service.
|
||||
required: false
|
||||
type: bool
|
||||
aliases: ["ipakrbrequirespreauth"]
|
||||
ok_as_delegate:
|
||||
description: Client credentials may be delegated to the service.
|
||||
required: false
|
||||
type: bool
|
||||
aliases: ["ipakrbokasdelegate"]
|
||||
ok_to_auth_as_delegate:
|
||||
description: Allow service to authenticate on behalf of a client.
|
||||
required: false
|
||||
type: bool
|
||||
aliases: ["ipakrboktoauthasdelegate"]
|
||||
principal:
|
||||
description: List of principal aliases for the service.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["krbprincipalname"]
|
||||
smb:
|
||||
description: Add a SMB service.
|
||||
required: false
|
||||
type: bool
|
||||
netbiosname:
|
||||
description: NETBIOS name for the SMB service.
|
||||
required: false
|
||||
type: str
|
||||
host:
|
||||
description: Host that can manage the service.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["managedby_host"]
|
||||
allow_create_keytab_user:
|
||||
description: Users allowed to create a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["ipaallowedtoperform_write_keys_user"]
|
||||
allow_create_keytab_group:
|
||||
description: Groups allowed to create a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["ipaallowedtoperform_write_keys_group"]
|
||||
allow_create_keytab_host:
|
||||
description: Hosts allowed to create a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["ipaallowedtoperform_write_keys_host"]
|
||||
allow_create_keytab_hostgroup:
|
||||
description: Host group allowed to create a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["ipaallowedtoperform_write_keys_hostgroup"]
|
||||
allow_retrieve_keytab_user:
|
||||
description: User allowed to retrieve a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["ipaallowedtoperform_read_keys_user"]
|
||||
allow_retrieve_keytab_group:
|
||||
description: Groups allowed to retrieve a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["ipaallowedtoperform_read_keys_group"]
|
||||
allow_retrieve_keytab_host:
|
||||
description: Hosts allowed to retrieve a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["ipaallowedtoperform_read_keys_host"]
|
||||
allow_retrieve_keytab_hostgroup:
|
||||
description: Host groups allowed to retrieve a keytab of this host.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
aliases: ["ipaallowedtoperform_read_keys_hostgroup"]
|
||||
certificate:
|
||||
description: Base-64 encoded service certificate.
|
||||
required: false
|
||||
@@ -63,7 +185,7 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: false
|
||||
choices: ["otp", "radius", "pkinit", "hardened", ""]
|
||||
choices: ["otp", "radius", "pkinit", "hardened", "idp", ""]
|
||||
aliases: ["krbprincipalauthind"]
|
||||
skip_host_check:
|
||||
description: Skip checking if host object exists.
|
||||
@@ -239,6 +361,15 @@ EXAMPLES = """
|
||||
- host1.example.com
|
||||
- host2.example.com
|
||||
action: member
|
||||
|
||||
# Ensure multiple services are present.
|
||||
- ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services:
|
||||
- name: HTTP/www.example.com
|
||||
host:
|
||||
- host1.example.com
|
||||
- name: HTTP/www.service.com
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
@@ -248,6 +379,9 @@ from ansible.module_utils.ansible_freeipa_module import \
|
||||
IPAAnsibleModule, compare_args_ipa, encode_certificate, \
|
||||
gen_add_del_lists, gen_add_list, gen_intersection_list, ipalib_errors, \
|
||||
api_get_realm, to_text
|
||||
from ansible.module_utils import six
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
|
||||
def find_service(module, name):
|
||||
@@ -321,8 +455,9 @@ def check_parameters(module, state, action, names):
|
||||
'allow_retrieve_keytab_hostgroup']
|
||||
|
||||
if state == 'present':
|
||||
if len(names) != 1:
|
||||
module.fail_json(msg="Only one service can be added at a time.")
|
||||
if names is not None and len(names) != 1:
|
||||
module.fail_json(msg="Only one service can be added at a time "
|
||||
"using 'name'.")
|
||||
|
||||
if action == 'service':
|
||||
invalid = ['delete_continue']
|
||||
@@ -338,9 +473,6 @@ def check_parameters(module, state, action, names):
|
||||
invalid.append('delete_continue')
|
||||
|
||||
elif state == 'absent':
|
||||
if len(names) < 1:
|
||||
module.fail_json(msg="No name given.")
|
||||
|
||||
if action == "service":
|
||||
invalid.extend(invalid_not_member)
|
||||
else:
|
||||
@@ -359,68 +491,96 @@ def check_parameters(module, state, action, names):
|
||||
module.params_fail_used_invalid(invalid, state, action)
|
||||
|
||||
|
||||
def check_authind(module, auth_ind):
|
||||
_invalid = module.ipa_command_invalid_param_choices(
|
||||
"service_add", "krbprincipalauthind", auth_ind)
|
||||
if _invalid:
|
||||
module.fail_json(
|
||||
msg="The use of krbprincipalauthind '%s' is not supported "
|
||||
"by your IPA version" % "','".join(_invalid))
|
||||
|
||||
|
||||
def init_ansible_module():
|
||||
service_spec = dict(
|
||||
# service attributesstr
|
||||
certificate=dict(type="list", elements="str",
|
||||
aliases=['usercertificate'],
|
||||
default=None, required=False),
|
||||
principal=dict(type="list", elements="str",
|
||||
aliases=["krbprincipalname"], default=None),
|
||||
smb=dict(type="bool", required=False),
|
||||
netbiosname=dict(type="str", required=False),
|
||||
pac_type=dict(type="list", elements="str",
|
||||
aliases=["ipakrbauthzdata"],
|
||||
choices=["MS-PAC", "PAD", "NONE", ""]),
|
||||
auth_ind=dict(type="list", elements="str",
|
||||
aliases=["krbprincipalauthind"],
|
||||
choices=["otp", "radius", "pkinit", "hardened", "idp",
|
||||
""]),
|
||||
skip_host_check=dict(type="bool"),
|
||||
force=dict(type="bool"),
|
||||
requires_pre_auth=dict(
|
||||
type="bool", aliases=["ipakrbrequirespreauth"]),
|
||||
ok_as_delegate=dict(type="bool", aliases=["ipakrbokasdelegate"]),
|
||||
ok_to_auth_as_delegate=dict(type="bool",
|
||||
aliases=["ipakrboktoauthasdelegate"]),
|
||||
host=dict(type="list", elements="str", aliases=["managedby_host"],
|
||||
required=False),
|
||||
allow_create_keytab_user=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_write_keys_user']),
|
||||
allow_retrieve_keytab_user=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_read_keys_user']),
|
||||
allow_create_keytab_group=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_write_keys_group']),
|
||||
allow_retrieve_keytab_group=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_read_keys_group']),
|
||||
allow_create_keytab_host=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_write_keys_host']),
|
||||
allow_retrieve_keytab_host=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_read_keys_host']),
|
||||
allow_create_keytab_hostgroup=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_write_keys_hostgroup']),
|
||||
allow_retrieve_keytab_hostgroup=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_read_keys_hostgroup']),
|
||||
delete_continue=dict(type="bool", required=False,
|
||||
aliases=['continue']),
|
||||
)
|
||||
ansible_module = IPAAnsibleModule(
|
||||
argument_spec=dict(
|
||||
# general
|
||||
name=dict(type="list", elements="str", aliases=["service"],
|
||||
required=True),
|
||||
# service attributesstr
|
||||
certificate=dict(type="list", elements="str",
|
||||
aliases=['usercertificate'],
|
||||
default=None, required=False),
|
||||
principal=dict(type="list", elements="str",
|
||||
aliases=["krbprincipalname"], default=None),
|
||||
smb=dict(type="bool", required=False),
|
||||
netbiosname=dict(type="str", required=False),
|
||||
pac_type=dict(type="list", elements="str",
|
||||
aliases=["ipakrbauthzdata"],
|
||||
choices=["MS-PAC", "PAD", "NONE", ""]),
|
||||
auth_ind=dict(type="list", elements="str",
|
||||
aliases=["krbprincipalauthind"],
|
||||
choices=["otp", "radius", "pkinit", "hardened", ""]),
|
||||
skip_host_check=dict(type="bool"),
|
||||
force=dict(type="bool"),
|
||||
requires_pre_auth=dict(
|
||||
type="bool", aliases=["ipakrbrequirespreauth"]),
|
||||
ok_as_delegate=dict(type="bool", aliases=["ipakrbokasdelegate"]),
|
||||
ok_to_auth_as_delegate=dict(type="bool",
|
||||
aliases=["ipakrboktoauthasdelegate"]),
|
||||
host=dict(type="list", elements="str", aliases=["managedby_host"],
|
||||
required=False),
|
||||
allow_create_keytab_user=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_write_keys_user']),
|
||||
allow_retrieve_keytab_user=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_read_keys_user']),
|
||||
allow_create_keytab_group=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_write_keys_group']),
|
||||
allow_retrieve_keytab_group=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_read_keys_group']),
|
||||
allow_create_keytab_host=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_write_keys_host']),
|
||||
allow_retrieve_keytab_host=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_read_keys_host']),
|
||||
allow_create_keytab_hostgroup=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_write_keys_hostgroup']),
|
||||
allow_retrieve_keytab_hostgroup=dict(
|
||||
type="list", elements="str", required=False, no_log=False,
|
||||
aliases=['ipaallowedtoperform_read_keys_hostgroup']),
|
||||
delete_continue=dict(type="bool", required=False,
|
||||
aliases=['continue']),
|
||||
default=None, required=False),
|
||||
services=dict(type="list",
|
||||
default=None,
|
||||
options=dict(
|
||||
# Here name is a simple string
|
||||
name=dict(type="str", required=True,
|
||||
aliases=["service"]),
|
||||
# Add service specific parameters
|
||||
**service_spec
|
||||
),
|
||||
elements='dict',
|
||||
required=False),
|
||||
# action
|
||||
action=dict(type="str", default="service",
|
||||
choices=["member", "service"]),
|
||||
# state
|
||||
state=dict(type="str", default="present",
|
||||
choices=["present", "absent", "disabled"]),
|
||||
|
||||
# Add service specific parameters for simple use case
|
||||
**service_spec
|
||||
),
|
||||
mutually_exclusive=[["name", "services"]],
|
||||
required_one_of=[["name", "services"]],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
@@ -436,10 +596,17 @@ def main():
|
||||
|
||||
# general
|
||||
names = ansible_module.params_get("name")
|
||||
services = ansible_module.params_get("services")
|
||||
|
||||
# service attributes
|
||||
principal = ansible_module.params_get("principal")
|
||||
certificate = ansible_module.params_get("certificate")
|
||||
# Any leading or trailing whitespace is removed while adding the
|
||||
# certificate with serive_add_cert. To be able to compare the results
|
||||
# from service_show with the given certificates we have to remove the
|
||||
# white space also.
|
||||
if certificate is not None:
|
||||
certificate = [cert.strip() for cert in certificate]
|
||||
pac_type = ansible_module.params_get("pac_type", allow_empty_string=True)
|
||||
auth_ind = ansible_module.params_get("auth_ind", allow_empty_string=True)
|
||||
skip_host_check = ansible_module.params_get("skip_host_check")
|
||||
@@ -462,8 +629,16 @@ def main():
|
||||
state = ansible_module.params_get("state")
|
||||
|
||||
# check parameters
|
||||
if (names is None or len(names) < 1) and \
|
||||
(services is None or len(services) < 1):
|
||||
ansible_module.fail_json(msg="At least one name or services is "
|
||||
"required")
|
||||
check_parameters(ansible_module, state, action, names)
|
||||
|
||||
# Use services if names is None
|
||||
if services is not None:
|
||||
names = services
|
||||
|
||||
# Init
|
||||
|
||||
changed = False
|
||||
@@ -477,11 +652,50 @@ def main():
|
||||
if skip_host_check and not has_skip_host_check:
|
||||
ansible_module.fail_json(
|
||||
msg="Skipping host check is not supported by your IPA version")
|
||||
check_authind(ansible_module, auth_ind)
|
||||
|
||||
commands = []
|
||||
keytab_members = ["user", "group", "host", "hostgroup"]
|
||||
service_set = set()
|
||||
|
||||
for name in names:
|
||||
for service in names:
|
||||
if isinstance(service, dict):
|
||||
name = service.get("name")
|
||||
if name in service_set:
|
||||
ansible_module.fail_json(
|
||||
msg="service '%s' is used more than once" % name)
|
||||
service_set.add(name)
|
||||
principal = service.get("principal")
|
||||
certificate = service.get("certificate")
|
||||
# Any leading or trailing whitespace is removed while adding
|
||||
# the certificate with serive_add_cert. To be able to compare
|
||||
# the results from service_show with the given certificates
|
||||
# we have to remove the white space also.
|
||||
if certificate is not None:
|
||||
certificate = [cert.strip() for cert in certificate]
|
||||
pac_type = service.get("pac_type")
|
||||
auth_ind = service.get("auth_ind")
|
||||
check_authind(ansible_module, auth_ind)
|
||||
skip_host_check = service.get("skip_host_check")
|
||||
if skip_host_check and not has_skip_host_check:
|
||||
ansible_module.fail_json(
|
||||
msg="Skipping host check is not supported by your IPA "
|
||||
"version")
|
||||
force = service.get("force")
|
||||
requires_pre_auth = service.get("requires_pre_auth")
|
||||
ok_as_delegate = service.get("ok_as_delegate")
|
||||
ok_to_auth_as_delegate = service.get("ok_to_auth_as_delegate")
|
||||
smb = service.get("smb")
|
||||
netbiosname = service.get("netbiosname")
|
||||
host = service.get("host")
|
||||
|
||||
delete_continue = service.get("delete_continue")
|
||||
|
||||
elif isinstance(service, (str, unicode)):
|
||||
name = service
|
||||
else:
|
||||
ansible_module.fail_json(msg="Service '%s' is not valid" %
|
||||
repr(service))
|
||||
res_find = find_service(ansible_module, name)
|
||||
res_principals = []
|
||||
|
||||
|
||||
@@ -80,6 +80,10 @@ options:
|
||||
description: The home directory
|
||||
type: str
|
||||
required: false
|
||||
gecos:
|
||||
description: The GECOS
|
||||
type: str
|
||||
required: false
|
||||
shell:
|
||||
description: The login shell
|
||||
type: str
|
||||
@@ -124,15 +128,19 @@ options:
|
||||
required: false
|
||||
type: bool
|
||||
uid:
|
||||
description: The UID
|
||||
description: User ID Number (system will assign one if not provided)
|
||||
type: int
|
||||
required: false
|
||||
aliases: ["uidnumber"]
|
||||
gid:
|
||||
description: The GID
|
||||
description: Group ID Number
|
||||
type: int
|
||||
required: false
|
||||
aliases: ["gidnumber"]
|
||||
street:
|
||||
description: Street address
|
||||
type: str
|
||||
required: false
|
||||
city:
|
||||
description: City
|
||||
type: str
|
||||
@@ -200,7 +208,7 @@ options:
|
||||
Use empty string to reset userauthtype to the initial value.
|
||||
type: list
|
||||
elements: str
|
||||
choices: ['password', 'radius', 'otp', '']
|
||||
choices: ["password", "radius", "otp", "pkinit", "hardened", "idp", ""]
|
||||
required: false
|
||||
aliases: ["ipauserauthtype"]
|
||||
userclass:
|
||||
@@ -234,10 +242,45 @@ options:
|
||||
description: Employee Type
|
||||
type: str
|
||||
required: false
|
||||
smb_logon_script:
|
||||
description: SMB logon script path
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["ipantlogonscript"]
|
||||
smb_profile_path:
|
||||
description: SMB profile path
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["ipantprofilepath"]
|
||||
smb_home_dir:
|
||||
description: SMB Home Directory
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["ipanthomedirectory"]
|
||||
smb_home_drive:
|
||||
description: SMB Home Directory Drive
|
||||
type: str
|
||||
required: false
|
||||
choices: [
|
||||
'A:', 'B:', 'C:', 'D:', 'E:', 'F:', 'G:', 'H:', 'I:', 'J:',
|
||||
'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:', 'S:', 'T:',
|
||||
'U:', 'V:', 'W:', 'X:', 'Y:', 'Z:', ''
|
||||
]
|
||||
aliases: ["ipanthomedirectorydrive"]
|
||||
preferredlanguage:
|
||||
description: Preferred Language
|
||||
type: str
|
||||
required: false
|
||||
idp:
|
||||
description: External IdP configuration
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["ipaidpconfiglink"]
|
||||
idp_user_id:
|
||||
description: A string that identifies the user at external IdP
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["ipaidpsub"]
|
||||
certificate:
|
||||
description: List of base-64 encoded user certificates
|
||||
type: list
|
||||
@@ -304,6 +347,10 @@ options:
|
||||
description: The home directory
|
||||
type: str
|
||||
required: false
|
||||
gecos:
|
||||
description: The GECOS
|
||||
type: str
|
||||
required: false
|
||||
shell:
|
||||
description: The login shell
|
||||
type: str
|
||||
@@ -348,15 +395,19 @@ options:
|
||||
required: false
|
||||
type: bool
|
||||
uid:
|
||||
description: The UID
|
||||
description: User ID Number (system will assign one if not provided)
|
||||
type: int
|
||||
required: false
|
||||
aliases: ["uidnumber"]
|
||||
gid:
|
||||
description: The GID
|
||||
description: Group ID Number
|
||||
type: int
|
||||
required: false
|
||||
aliases: ["gidnumber"]
|
||||
street:
|
||||
description: Street address
|
||||
type: str
|
||||
required: false
|
||||
city:
|
||||
description: City
|
||||
type: str
|
||||
@@ -424,7 +475,7 @@ options:
|
||||
Use empty string to reset userauthtype to the initial value.
|
||||
type: list
|
||||
elements: str
|
||||
choices: ['password', 'radius', 'otp', '']
|
||||
choices: ["password", "radius", "otp", "pkinit", "hardened", "idp", ""]
|
||||
required: false
|
||||
aliases: ["ipauserauthtype"]
|
||||
userclass:
|
||||
@@ -458,10 +509,45 @@ options:
|
||||
description: Employee Type
|
||||
type: str
|
||||
required: false
|
||||
smb_logon_script:
|
||||
description: SMB logon script path
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["ipantlogonscript"]
|
||||
smb_profile_path:
|
||||
description: SMB profile path
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["ipantprofilepath"]
|
||||
smb_home_dir:
|
||||
description: SMB Home Directory
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["ipanthomedirectory"]
|
||||
smb_home_drive:
|
||||
description: SMB Home Directory Drive
|
||||
type: str
|
||||
required: false
|
||||
choices: [
|
||||
'A:', 'B:', 'C:', 'D:', 'E:', 'F:', 'G:', 'H:', 'I:', 'J:',
|
||||
'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:', 'S:', 'T:',
|
||||
'U:', 'V:', 'W:', 'X:', 'Y:', 'Z:', ''
|
||||
]
|
||||
aliases: ["ipanthomedirectorydrive"]
|
||||
preferredlanguage:
|
||||
description: Preferred Language
|
||||
type: str
|
||||
required: false
|
||||
idp:
|
||||
description: External IdP configuration
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["ipaidpconfiglink"]
|
||||
idp_user_id:
|
||||
description: A string that identifies the user at external IdP
|
||||
type: str
|
||||
required: false
|
||||
aliases: ["ipaidpsub"]
|
||||
certificate:
|
||||
description: List of base-64 encoded user certificates
|
||||
type: list
|
||||
@@ -548,6 +634,17 @@ EXAMPLES = """
|
||||
first: brain
|
||||
last: Acme
|
||||
|
||||
# Create multiple users pinky and brain
|
||||
- ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
users:
|
||||
- name: pinky
|
||||
first: pinky
|
||||
last: Acme
|
||||
- name: brain
|
||||
first: brain
|
||||
last: Acme
|
||||
|
||||
# Delete user pinky, but preserved
|
||||
- ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
@@ -573,11 +670,30 @@ EXAMPLES = """
|
||||
name: pinky,brain
|
||||
state: enabled
|
||||
|
||||
# Remove but preserve user pinky
|
||||
- ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
users:
|
||||
- name: pinky
|
||||
preserve: yes
|
||||
state: absent
|
||||
|
||||
# Remove user pinky and brain
|
||||
- ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: pinky,brain
|
||||
state: disabled
|
||||
|
||||
# Ensure a user has 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:"
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
@@ -589,10 +705,12 @@ user:
|
||||
randompassword:
|
||||
description: The generated random password
|
||||
type: str
|
||||
returned: If only one user is handled by the module
|
||||
returned: |
|
||||
If only one user is handled by the module without using users parameter
|
||||
name:
|
||||
description: The user name of the user that got a new random password
|
||||
returned: If several users are handled by the module
|
||||
returned: |
|
||||
If several users are handled by the module with the users parameter
|
||||
type: dict
|
||||
contains:
|
||||
randompassword:
|
||||
@@ -631,12 +749,14 @@ def find_user(module, name):
|
||||
return _result
|
||||
|
||||
|
||||
def gen_args(first, last, fullname, displayname, initials, homedir, shell,
|
||||
email, principalexpiration, passwordexpiration, password,
|
||||
random, uid, gid, city, userstate, postalcode, phone, mobile,
|
||||
pager, fax, orgunit, title, carlicense, sshpubkey, userauthtype,
|
||||
userclass, radius, radiususer, departmentnumber, employeenumber,
|
||||
employeetype, preferredlanguage, noprivate, nomembers):
|
||||
def gen_args(first, last, fullname, displayname, initials, homedir, gecos,
|
||||
shell, email, principalexpiration, passwordexpiration, password,
|
||||
random, uid, gid, street, city, userstate, postalcode, phone,
|
||||
mobile, pager, fax, orgunit, title, carlicense, sshpubkey,
|
||||
userauthtype, userclass, radius, radiususer, departmentnumber,
|
||||
employeenumber, employeetype, preferredlanguage, smb_logon_script,
|
||||
smb_profile_path, smb_home_dir, smb_home_drive, idp, idp_user_id,
|
||||
noprivate, nomembers):
|
||||
# principal, manager, certificate and certmapdata are handled not in here
|
||||
_args = {}
|
||||
if first is not None:
|
||||
@@ -651,6 +771,8 @@ def gen_args(first, last, fullname, displayname, initials, homedir, shell,
|
||||
_args["initials"] = initials
|
||||
if homedir is not None:
|
||||
_args["homedirectory"] = homedir
|
||||
if gecos is not None:
|
||||
_args["gecos"] = gecos
|
||||
if shell is not None:
|
||||
_args["loginshell"] = shell
|
||||
if email is not None and len(email) > 0:
|
||||
@@ -667,6 +789,8 @@ def gen_args(first, last, fullname, displayname, initials, homedir, shell,
|
||||
_args["uidnumber"] = to_text(str(uid))
|
||||
if gid is not None:
|
||||
_args["gidnumber"] = to_text(str(gid))
|
||||
if street is not None:
|
||||
_args["street"] = street
|
||||
if city is not None:
|
||||
_args["l"] = city
|
||||
if userstate is not None:
|
||||
@@ -705,48 +829,57 @@ def gen_args(first, last, fullname, displayname, initials, homedir, shell,
|
||||
_args["employeetype"] = employeetype
|
||||
if preferredlanguage is not None:
|
||||
_args["preferredlanguage"] = preferredlanguage
|
||||
if idp is not None:
|
||||
_args["ipaidpconfiglink"] = idp
|
||||
if idp_user_id is not None:
|
||||
_args["ipaidpsub"] = idp_user_id
|
||||
if noprivate is not None:
|
||||
_args["noprivate"] = noprivate
|
||||
if nomembers is not None:
|
||||
_args["no_members"] = nomembers
|
||||
if smb_logon_script is not None:
|
||||
_args["ipantlogonscript"] = smb_logon_script
|
||||
if smb_profile_path is not None:
|
||||
_args["ipantprofilepath"] = smb_profile_path
|
||||
if smb_home_dir is not None:
|
||||
_args["ipanthomedirectory"] = smb_home_dir
|
||||
if smb_home_drive is not None:
|
||||
_args["ipanthomedirectorydrive"] = smb_home_drive
|
||||
return _args
|
||||
|
||||
|
||||
def check_parameters( # pylint: disable=unused-argument
|
||||
module, state, action, first, last, fullname, displayname, initials,
|
||||
homedir, shell, email, principal, principalexpiration,
|
||||
passwordexpiration, password, random, uid, gid, city, phone, mobile,
|
||||
pager, fax, orgunit, title, manager, carlicense, sshpubkey,
|
||||
homedir, gecos, shell, email, principal, principalexpiration,
|
||||
passwordexpiration, password, random, uid, gid, street, city, phone,
|
||||
mobile, pager, fax, orgunit, title, manager, carlicense, sshpubkey,
|
||||
userauthtype, userclass, radius, radiususer, departmentnumber,
|
||||
employeenumber, employeetype, preferredlanguage, certificate,
|
||||
certmapdata, noprivate, nomembers, preserve, update_password):
|
||||
invalid = []
|
||||
if state == "present":
|
||||
if action == "member":
|
||||
invalid = ["first", "last", "fullname", "displayname", "initials",
|
||||
"homedir", "shell", "email", "principalexpiration",
|
||||
"passwordexpiration", "password", "random", "uid",
|
||||
"gid", "city", "phone", "mobile", "pager", "fax",
|
||||
"orgunit", "title", "carlicense", "sshpubkey",
|
||||
"userauthtype", "userclass", "radius", "radiususer",
|
||||
"departmentnumber", "employeenumber", "employeetype",
|
||||
"preferredlanguage", "noprivate", "nomembers",
|
||||
"preserve", "update_password"]
|
||||
|
||||
certmapdata, noprivate, nomembers, preserve, update_password,
|
||||
smb_logon_script, smb_profile_path, smb_home_dir, smb_home_drive,
|
||||
idp, ipa_user_id,
|
||||
):
|
||||
if state == "present" and action == "user":
|
||||
invalid = ["preserve"]
|
||||
else:
|
||||
invalid = ["first", "last", "fullname", "displayname", "initials",
|
||||
"homedir", "shell", "email", "principalexpiration",
|
||||
"passwordexpiration", "password", "random", "uid",
|
||||
"gid", "city", "phone", "mobile", "pager", "fax",
|
||||
"orgunit", "title", "carlicense", "sshpubkey",
|
||||
"userauthtype", "userclass", "radius", "radiususer",
|
||||
"departmentnumber", "employeenumber", "employeetype",
|
||||
"preferredlanguage", "noprivate", "nomembers",
|
||||
"update_password"]
|
||||
if action == "user":
|
||||
invalid.extend(["principal", "manager",
|
||||
"certificate", "certmapdata",
|
||||
])
|
||||
invalid = [
|
||||
"first", "last", "fullname", "displayname", "initials", "homedir",
|
||||
"shell", "email", "principalexpiration", "passwordexpiration",
|
||||
"password", "random", "uid", "gid", "street", "city", "phone",
|
||||
"mobile", "pager", "fax", "orgunit", "title", "carlicense",
|
||||
"sshpubkey", "userauthtype", "userclass", "radius", "radiususer",
|
||||
"departmentnumber", "employeenumber", "employeetype",
|
||||
"preferredlanguage", "noprivate", "nomembers", "update_password",
|
||||
"gecos", "smb_logon_script", "smb_profile_path", "smb_home_dir",
|
||||
"smb_home_drive", "idp", "idp_user_id"
|
||||
]
|
||||
|
||||
if state == "present" and action == "member":
|
||||
invalid.append("preserve")
|
||||
else:
|
||||
if action == "user":
|
||||
invalid.extend(
|
||||
["principal", "manager", "certificate", "certmapdata"])
|
||||
|
||||
if state != "absent" and preserve is not None:
|
||||
module.fail_json(
|
||||
@@ -780,6 +913,15 @@ def check_parameters( # pylint: disable=unused-argument
|
||||
module.fail_json(msg="certmapdata: subject is missing")
|
||||
|
||||
|
||||
def check_userauthtype(module, userauthtype):
|
||||
_invalid = module.ipa_command_invalid_param_choices(
|
||||
"user_add", "ipauserauthtype", userauthtype)
|
||||
if _invalid:
|
||||
module.fail_json(
|
||||
msg="The use of userauthtype '%s' is not supported "
|
||||
"by your IPA version" % "','".join(_invalid))
|
||||
|
||||
|
||||
def extend_emails(email, default_email_domain):
|
||||
if email is not None:
|
||||
return ["%s@%s" % (_email, default_email_domain)
|
||||
@@ -834,11 +976,11 @@ def gen_certmapdata_args(certmapdata):
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def result_handler(module, result, command, name, args, errors, exit_args,
|
||||
one_name):
|
||||
single_user):
|
||||
|
||||
if "random" in args and command in ["user_add", "user_mod"] \
|
||||
and "randompassword" in result["result"]:
|
||||
if one_name:
|
||||
if single_user:
|
||||
exit_args["randompassword"] = \
|
||||
result["result"]["randompassword"]
|
||||
else:
|
||||
@@ -861,7 +1003,7 @@ def result_handler(module, result, command, name, args, errors, exit_args,
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def exception_handler(module, ex, errors, exit_args, one_name):
|
||||
def exception_handler(module, ex, errors, exit_args, single_user):
|
||||
msg = str(ex)
|
||||
if "already contains" in msg \
|
||||
or "does not contain" in msg:
|
||||
@@ -881,6 +1023,7 @@ def main():
|
||||
displayname=dict(type="str", default=None),
|
||||
initials=dict(type="str", default=None),
|
||||
homedir=dict(type="str", default=None),
|
||||
gecos=dict(type="str", default=None),
|
||||
shell=dict(type="str", aliases=["loginshell"], default=None),
|
||||
email=dict(type="list", elements="str", default=None),
|
||||
principal=dict(type="list", elements="str",
|
||||
@@ -896,6 +1039,7 @@ def main():
|
||||
random=dict(type='bool', default=None),
|
||||
uid=dict(type="int", aliases=["uidnumber"], default=None),
|
||||
gid=dict(type="int", aliases=["gidnumber"], default=None),
|
||||
street=dict(type="str", default=None),
|
||||
city=dict(type="str", default=None),
|
||||
userstate=dict(type="str", aliases=["st"], default=None),
|
||||
postalcode=dict(type="str", aliases=["zip"], default=None),
|
||||
@@ -913,7 +1057,8 @@ def main():
|
||||
default=None),
|
||||
userauthtype=dict(type='list', elements="str",
|
||||
aliases=["ipauserauthtype"], default=None,
|
||||
choices=['password', 'radius', 'otp', '']),
|
||||
choices=["password", "radius", "otp", "pkinit",
|
||||
"hardened", "idp", ""]),
|
||||
userclass=dict(type="list", elements="str", aliases=["class"],
|
||||
default=None),
|
||||
radius=dict(type="str", aliases=["ipatokenradiusconfiglink"],
|
||||
@@ -924,6 +1069,17 @@ def main():
|
||||
departmentnumber=dict(type="list", elements="str", default=None),
|
||||
employeenumber=dict(type="str", default=None),
|
||||
employeetype=dict(type="str", default=None),
|
||||
smb_logon_script=dict(type="str", default=None,
|
||||
aliases=["ipantlogonscript"]),
|
||||
smb_profile_path=dict(type="str", default=None,
|
||||
aliases=["ipantprofilepath"]),
|
||||
smb_home_dir=dict(type="str", default=None,
|
||||
aliases=["ipanthomedirectory"]),
|
||||
smb_home_drive=dict(type="str", default=None,
|
||||
choices=[
|
||||
("%c:" % chr(x))
|
||||
for x in range(ord('A'), ord('Z') + 1)
|
||||
] + [""], aliases=["ipanthomedirectorydrive"]),
|
||||
preferredlanguage=dict(type="str", default=None),
|
||||
certificate=dict(type="list", elements="str",
|
||||
aliases=["usercertificate"], default=None),
|
||||
@@ -938,6 +1094,9 @@ def main():
|
||||
elements='dict', required=False),
|
||||
noprivate=dict(type='bool', default=None),
|
||||
nomembers=dict(type='bool', default=None),
|
||||
idp=dict(type="str", default=None, aliases=['ipaidpconfiglink']),
|
||||
idp_user_id=dict(type="str", default=None,
|
||||
aliases=['ipaidpconfiglink']),
|
||||
)
|
||||
|
||||
ansible_module = IPAAnsibleModule(
|
||||
@@ -994,6 +1153,7 @@ def main():
|
||||
displayname = ansible_module.params_get("displayname")
|
||||
initials = ansible_module.params_get("initials")
|
||||
homedir = ansible_module.params_get("homedir")
|
||||
gecos = ansible_module.params_get("gecos")
|
||||
shell = ansible_module.params_get("shell")
|
||||
email = ansible_module.params_get("email")
|
||||
principal = ansible_module.params_get("principal")
|
||||
@@ -1012,6 +1172,7 @@ def main():
|
||||
random = ansible_module.params_get("random")
|
||||
uid = ansible_module.params_get("uid")
|
||||
gid = ansible_module.params_get("gid")
|
||||
street = ansible_module.params_get("street")
|
||||
city = ansible_module.params_get("city")
|
||||
userstate = ansible_module.params_get("userstate")
|
||||
postalcode = ansible_module.params_get("postalcode")
|
||||
@@ -1034,6 +1195,12 @@ def main():
|
||||
employeenumber = ansible_module.params_get("employeenumber")
|
||||
employeetype = ansible_module.params_get("employeetype")
|
||||
preferredlanguage = ansible_module.params_get("preferredlanguage")
|
||||
smb_logon_script = ansible_module.params_get("smb_logon_script")
|
||||
smb_profile_path = ansible_module.params_get("smb_profile_path")
|
||||
smb_home_dir = ansible_module.params_get("smb_home_dir")
|
||||
smb_home_drive = ansible_module.params_get("smb_home_drive")
|
||||
idp = ansible_module.params_get("idp")
|
||||
idp_user_id = ansible_module.params_get("idp_user_id")
|
||||
certificate = ansible_module.params_get("certificate")
|
||||
certmapdata = ansible_module.params_get("certmapdata")
|
||||
noprivate = ansible_module.params_get("noprivate")
|
||||
@@ -1059,13 +1226,15 @@ def main():
|
||||
|
||||
check_parameters(
|
||||
ansible_module, state, action,
|
||||
first, last, fullname, displayname, initials, homedir, shell, email,
|
||||
first, last, fullname, displayname, initials, homedir, gecos, shell,
|
||||
email,
|
||||
principal, principalexpiration, passwordexpiration, password, random,
|
||||
uid, gid, city, phone, mobile, pager, fax, orgunit, title, manager,
|
||||
carlicense, sshpubkey, userauthtype, userclass, radius, radiususer,
|
||||
departmentnumber, employeenumber, employeetype, preferredlanguage,
|
||||
certificate, certmapdata, noprivate, nomembers, preserve,
|
||||
update_password)
|
||||
uid, gid, street, city, phone, mobile, pager, fax, orgunit, title,
|
||||
manager, carlicense, sshpubkey, userauthtype, userclass, radius,
|
||||
radiususer, departmentnumber, employeenumber, employeetype,
|
||||
preferredlanguage, certificate, certmapdata, noprivate, nomembers,
|
||||
preserve, update_password, smb_logon_script, smb_profile_path,
|
||||
smb_home_dir, smb_home_drive, idp, idp_user_id)
|
||||
certmapdata = convert_certmapdata(certmapdata)
|
||||
|
||||
# Use users if names is None
|
||||
@@ -1084,6 +1253,10 @@ def main():
|
||||
|
||||
server_realm = ansible_module.ipa_get_realm()
|
||||
|
||||
# Check API specific parameters
|
||||
|
||||
check_userauthtype(ansible_module, userauthtype)
|
||||
|
||||
# Default email domain
|
||||
|
||||
result = ansible_module.ipa_command_no_name("config_show", {})
|
||||
@@ -1112,6 +1285,7 @@ def main():
|
||||
displayname = user.get("displayname")
|
||||
initials = user.get("initials")
|
||||
homedir = user.get("homedir")
|
||||
gecos = user.get("gecos")
|
||||
shell = user.get("shell")
|
||||
email = user.get("email")
|
||||
principal = user.get("principal")
|
||||
@@ -1129,6 +1303,7 @@ def main():
|
||||
random = user.get("random")
|
||||
uid = user.get("uid")
|
||||
gid = user.get("gid")
|
||||
street = user.get("street")
|
||||
city = user.get("city")
|
||||
userstate = user.get("userstate")
|
||||
postalcode = user.get("postalcode")
|
||||
@@ -1149,6 +1324,12 @@ def main():
|
||||
employeenumber = user.get("employeenumber")
|
||||
employeetype = user.get("employeetype")
|
||||
preferredlanguage = user.get("preferredlanguage")
|
||||
smb_logon_script = user.get("smb_logon_script")
|
||||
smb_profile_path = user.get("smb_profile_path")
|
||||
smb_home_dir = user.get("smb_home_dir")
|
||||
smb_home_drive = user.get("smb_home_drive")
|
||||
idp = user.get("idp")
|
||||
idp_user_id = user.get("idp_user_id")
|
||||
certificate = user.get("certificate")
|
||||
certmapdata = user.get("certmapdata")
|
||||
noprivate = user.get("noprivate")
|
||||
@@ -1157,16 +1338,21 @@ def main():
|
||||
check_parameters(
|
||||
ansible_module, state, action,
|
||||
first, last, fullname, displayname, initials, homedir,
|
||||
shell, email, principal, principalexpiration,
|
||||
passwordexpiration, password, random, uid, gid, city,
|
||||
phone, mobile, pager, fax, orgunit, title, manager,
|
||||
gecos, shell, email, principal, principalexpiration,
|
||||
passwordexpiration, password, random, uid, gid, street,
|
||||
city, phone, mobile, pager, fax, orgunit, title, manager,
|
||||
carlicense, sshpubkey, userauthtype, userclass, radius,
|
||||
radiususer, departmentnumber, employeenumber,
|
||||
employeetype, preferredlanguage, certificate,
|
||||
certmapdata, noprivate, nomembers, preserve,
|
||||
update_password)
|
||||
update_password, smb_logon_script, smb_profile_path,
|
||||
smb_home_dir, smb_home_drive, idp, idp_user_id)
|
||||
certmapdata = convert_certmapdata(certmapdata)
|
||||
|
||||
# Check API specific parameters
|
||||
|
||||
check_userauthtype(ansible_module, userauthtype)
|
||||
|
||||
# Extend email addresses
|
||||
|
||||
email = extend_emails(email, default_email_domain)
|
||||
@@ -1206,6 +1392,34 @@ def main():
|
||||
msg="The use of certmapdata is not supported by "
|
||||
"your IPA version")
|
||||
|
||||
# Check if SMB attributes are available
|
||||
if (
|
||||
any([
|
||||
smb_logon_script, smb_profile_path, smb_home_dir,
|
||||
smb_home_drive
|
||||
])
|
||||
and not ansible_module.ipa_command_param_exists(
|
||||
"user_mod", "ipanthomedirectory"
|
||||
)
|
||||
):
|
||||
ansible_module.fail_json(
|
||||
msg="The use of smb_logon_script, smb_profile_path, "
|
||||
"smb_profile_path, and smb_home_drive is not supported "
|
||||
"by your IPA version")
|
||||
|
||||
# Check if IdP support is available
|
||||
require_idp = (
|
||||
idp is not None
|
||||
or idp_user_id is not None
|
||||
or userauthtype == "idp"
|
||||
)
|
||||
has_idp_support = ansible_module.ipa_command_param_exists(
|
||||
"user_add", "ipaidpconfiglink"
|
||||
)
|
||||
if require_idp and not has_idp_support:
|
||||
ansible_module.fail_json(
|
||||
msg="Your IPA version does not support External IdP.")
|
||||
|
||||
# Make sure user exists
|
||||
res_find = find_user(ansible_module, name)
|
||||
|
||||
@@ -1214,12 +1428,16 @@ def main():
|
||||
# Generate args
|
||||
args = gen_args(
|
||||
first, last, fullname, displayname, initials, homedir,
|
||||
gecos,
|
||||
shell, email, principalexpiration, passwordexpiration,
|
||||
password, random, uid, gid, city, userstate, postalcode,
|
||||
phone, mobile, pager, fax, orgunit, title, carlicense,
|
||||
sshpubkey, userauthtype, userclass, radius, radiususer,
|
||||
departmentnumber, employeenumber, employeetype,
|
||||
preferredlanguage, noprivate, nomembers)
|
||||
password, random, uid, gid, street, city, userstate,
|
||||
postalcode, phone, mobile, pager, fax, orgunit, title,
|
||||
carlicense, sshpubkey, userauthtype, userclass, radius,
|
||||
radiususer, departmentnumber, employeenumber, employeetype,
|
||||
preferredlanguage, smb_logon_script, smb_profile_path,
|
||||
smb_home_dir, smb_home_drive, idp, idp_user_id, noprivate,
|
||||
nomembers,
|
||||
)
|
||||
|
||||
if action == "user":
|
||||
# Found the user
|
||||
@@ -1255,8 +1473,21 @@ def main():
|
||||
ansible_module.fail_json(
|
||||
msg="Last name is needed")
|
||||
|
||||
smb_attrs = {
|
||||
k: args[k]
|
||||
for k in [
|
||||
"ipanthomedirectory",
|
||||
"ipanthomedirectorydrive",
|
||||
"ipantlogonscript",
|
||||
"ipantprofilepath",
|
||||
]
|
||||
if k in args
|
||||
}
|
||||
for key in smb_attrs.keys():
|
||||
del args[key]
|
||||
commands.append([name, "user_add", args])
|
||||
|
||||
if smb_attrs:
|
||||
commands.append([name, "user_mod", smb_attrs])
|
||||
# Handle members: principal, manager, certificate and
|
||||
# certmapdata
|
||||
if res_find is not None:
|
||||
@@ -1511,7 +1742,7 @@ def main():
|
||||
|
||||
changed = ansible_module.execute_ipa_commands(
|
||||
commands, result_handler, exception_handler,
|
||||
exit_args=exit_args, one_name=len(names) == 1)
|
||||
exit_args=exit_args, single_user=users is None)
|
||||
|
||||
# Done
|
||||
ansible_module.exit_json(changed=changed, user=exit_args)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
-r requirements-tests.txt
|
||||
ipdb==0.13.4
|
||||
pre-commit
|
||||
flake8==4.0.1
|
||||
pre-commit==2.20.0
|
||||
flake8==5.0.3
|
||||
flake8-bugbear==22.10.27
|
||||
pylint==2.13.7
|
||||
wrapt >= 1.14.0
|
||||
pylint==2.14.4
|
||||
wrapt == 1.14.0
|
||||
pydocstyle==6.0.0
|
||||
yamllint==1.26.3
|
||||
ansible-lint==5.3.2
|
||||
yamllint==1.28.0
|
||||
ansible-lint==6.6.1
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
-r requirements.txt
|
||||
pytest>=2.7
|
||||
pytest-sourceorder>=0.5
|
||||
pytest==7.1.3
|
||||
pytest-sourceorder==0.6.0
|
||||
pytest-split>=0.8.0
|
||||
pytest-custom_exit_code>=0.3.0
|
||||
pytest-testinfra>=5.0
|
||||
pytest-testinfra==6.8.0
|
||||
pytest-randomly==3.12.0
|
||||
pyyaml>=3
|
||||
|
||||
@@ -34,7 +34,7 @@ Supported Distributions
|
||||
|
||||
* RHEL/CentOS 7.6+
|
||||
* Fedora 26+
|
||||
* Ubuntu
|
||||
* Ubuntu 16.04 and 18.04
|
||||
|
||||
|
||||
Requirements
|
||||
|
||||
@@ -6,15 +6,15 @@ galaxy_info:
|
||||
description: A role to backup and restore an IPA server
|
||||
company: Red Hat, Inc
|
||||
license: GPLv3
|
||||
min_ansible_version: 2.8
|
||||
min_ansible_version: "2.8"
|
||||
platforms:
|
||||
- name: Fedora
|
||||
versions:
|
||||
- all
|
||||
- name: EL
|
||||
versions:
|
||||
- 7
|
||||
- 8
|
||||
- "7"
|
||||
- "8"
|
||||
galaxy_tags:
|
||||
- identity
|
||||
- ipa
|
||||
|
||||
@@ -2,20 +2,22 @@
|
||||
# tasks file for ipabackup
|
||||
|
||||
- name: Create backup
|
||||
shell: >
|
||||
ansible.builtin.shell: >
|
||||
ipa-backup
|
||||
{{ "--gpg" if ipabackup_gpg | bool else "" }}
|
||||
{{ "--gpg-keyring="+ipabackup_gpg_keyring if ipabackup_gpg_keyring is defined else "" }}
|
||||
{{ "--gpg-keyring=" + ipabackup_gpg_keyring if ipabackup_gpg_keyring is defined else "" }}
|
||||
{{ "--data" if ipabackup_data | bool else "" }}
|
||||
{{ "--logs" if ipabackup_logs | bool else "" }}
|
||||
{{ "--online" if ipabackup_online | bool else "" }}
|
||||
{{ "--disable-role-check" if ipabackup_disable_role_check | bool else "" }}
|
||||
{{ "--log-file="+ipabackup_log_file if ipabackup_log_file is defined else "" }}
|
||||
{{ "--log-file=" + ipabackup_log_file if ipabackup_log_file is defined else "" }}
|
||||
register: result_ipabackup
|
||||
|
||||
- block:
|
||||
- name: Handle backup
|
||||
when: ipabackup_to_controller
|
||||
block:
|
||||
- name: Get ipabackup_item from stderr or stdout output
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipabackup_item: "{{ item | regex_search('\n.*/([^\n]+)','\\1') | first }}"
|
||||
when: item.find("Backed up to "+ipabackup_dir+"/") > 0
|
||||
with_items:
|
||||
@@ -25,15 +27,14 @@
|
||||
label: ""
|
||||
|
||||
- name: Fail on missing ipabackup_item
|
||||
fail: msg="Failed to get ipabackup_item"
|
||||
ansible.builtin.fail:
|
||||
msg: "Failed to get ipabackup_item"
|
||||
when: ipabackup_item is not defined
|
||||
|
||||
- name: Copy backup to controller
|
||||
include_tasks: "{{ role_path }}/tasks/copy_backup_from_server.yml"
|
||||
ansible.builtin.include_tasks: "{{ role_path }}/tasks/copy_backup_from_server.yml"
|
||||
when: state|default("present") == "present"
|
||||
|
||||
- name: Remove backup on server
|
||||
include_tasks: "{{ role_path }}/tasks/remove_backup_from_server.yml"
|
||||
ansible.builtin.include_tasks: "{{ role_path }}/tasks/remove_backup_from_server.yml"
|
||||
when: not ipabackup_keep_on_server
|
||||
|
||||
when: ipabackup_to_controller
|
||||
|
||||
@@ -1,45 +1,47 @@
|
||||
---
|
||||
- name: Fail on invalid ipabackup_item
|
||||
fail: msg="ipabackup_item {{ ipabackup_item }} is not valid"
|
||||
ansible.builtin.fail:
|
||||
msg: "ipabackup_item {{ ipabackup_item }} is not valid"
|
||||
when: ipabackup_item is not defined or
|
||||
ipabackup_item | length < 1 or
|
||||
(ipabackup_item.find("ipa-full-") == -1 and
|
||||
ipabackup_item.find("ipa-data-") == -1)
|
||||
|
||||
- name: Set controller destination directory
|
||||
set_fact:
|
||||
ipabackup_controller_dir:
|
||||
"{{ ipabackup_controller_path | default(lookup('env','PWD')) }}/{{
|
||||
ansible.builtin.set_fact:
|
||||
__derived_controller_dir:
|
||||
"{{ ipabackup_controller_path | default(lookup('env', 'PWD')) }}/{{
|
||||
ipabackup_name_prefix | default(ansible_facts['fqdn']) }}_{{
|
||||
ipabackup_item }}/"
|
||||
|
||||
- name: Stat backup on server
|
||||
stat:
|
||||
ansible.builtin.stat:
|
||||
path: "{{ ipabackup_dir }}/{{ ipabackup_item }}"
|
||||
register: result_backup_stat
|
||||
|
||||
- name: Fail on missing backup directory
|
||||
fail: msg="Unable to find backup {{ ipabackup_item }}"
|
||||
ansible.builtin.fail:
|
||||
msg: "Unable to find backup {{ ipabackup_item }}"
|
||||
when: result_backup_stat.stat.isdir is not defined
|
||||
|
||||
- name: Get backup files to copy for "{{ ipabackup_item }}"
|
||||
shell:
|
||||
ansible.builtin.shell:
|
||||
find . -type f | cut -d"/" -f 2
|
||||
args:
|
||||
chdir: "{{ ipabackup_dir }}/{{ ipabackup_item }}"
|
||||
register: result_find_backup_files
|
||||
|
||||
- name: Copy server backup files to controller
|
||||
fetch:
|
||||
ansible.builtin.fetch:
|
||||
flat: yes
|
||||
src: "{{ ipabackup_dir }}/{{ ipabackup_item }}/{{ item }}"
|
||||
dest: "{{ ipabackup_controller_dir }}"
|
||||
dest: "{{ __derived_controller_dir }}"
|
||||
with_items:
|
||||
- "{{ result_find_backup_files.stdout_lines }}"
|
||||
|
||||
- name: Fix file modes for backup on controller
|
||||
file:
|
||||
dest: "{{ ipabackup_controller_dir }}"
|
||||
ansible.builtin.file:
|
||||
dest: "{{ __derived_controller_dir }}"
|
||||
mode: u=rwX,go=
|
||||
recurse: yes
|
||||
delegate_to: localhost
|
||||
|
||||
@@ -1,41 +1,43 @@
|
||||
---
|
||||
- name: Fail on invalid ipabackup_name
|
||||
fail: msg="ipabackup_name {{ ipabackup_name }} is not valid"
|
||||
ansible.builtin.fail:
|
||||
msg: "ipabackup_name {{ ipabackup_name }} is not valid"
|
||||
when: ipabackup_name is not defined or
|
||||
ipabackup_name | length < 1 or
|
||||
(ipabackup_name.find("ipa-full-") == -1 and
|
||||
ipabackup_name.find("ipa-data-") == -1)
|
||||
|
||||
- name: Set controller source directory
|
||||
set_fact:
|
||||
ipabackup_controller_dir:
|
||||
"{{ ipabackup_controller_path | default(lookup('env','PWD')) }}"
|
||||
ansible.builtin.set_fact:
|
||||
__derived_controller_dir:
|
||||
"{{ ipabackup_controller_path | default(lookup('env', 'PWD')) }}"
|
||||
|
||||
- name: Set ipabackup_item
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipabackup_item:
|
||||
"{{ ipabackup_name | regex_search('.*_(ipa-.+)','\\1') | first }}"
|
||||
"{{ ipabackup_name | regex_search('.*_(ipa-.+)', '\\1') | first }}"
|
||||
when: "'_ipa-' in ipabackup_name"
|
||||
|
||||
- name: Set ipabackup_item
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipabackup_item: "{{ ipabackup_name }}"
|
||||
when: "'_ipa-' not in ipabackup_name"
|
||||
|
||||
- name: Stat backup to copy
|
||||
stat:
|
||||
path: "{{ ipabackup_controller_dir }}/{{ ipabackup_name }}"
|
||||
ansible.builtin.stat:
|
||||
path: "{{ __derived_controller_dir }}/{{ ipabackup_name }}"
|
||||
register: result_backup_stat
|
||||
delegate_to: localhost
|
||||
become: no
|
||||
|
||||
- name: Fail on missing backup to copy
|
||||
fail: msg="Unable to find backup {{ ipabackup_name }}"
|
||||
ansible.builtin.fail:
|
||||
msg: "Unable to find backup {{ ipabackup_name }}"
|
||||
when: result_backup_stat.stat.isdir is not defined
|
||||
|
||||
- name: Copy backup files to server for "{{ ipabackup_item }}"
|
||||
copy:
|
||||
src: "{{ ipabackup_controller_dir }}/{{ ipabackup_name }}/"
|
||||
ansible.builtin.copy:
|
||||
src: "{{ __derived_controller_dir }}/{{ ipabackup_name }}/"
|
||||
dest: "{{ ipabackup_dir }}/{{ ipabackup_item }}"
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
register: result_ipabackup_get_backup_dir
|
||||
|
||||
- name: Set IPA backup dir
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipabackup_dir: "{{ result_ipabackup_get_backup_dir.backup_dir }}"
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
# tasks file for ipabackup
|
||||
|
||||
- name: Check for empty vars
|
||||
fail: msg="Variable {{ item }} is empty"
|
||||
ansible.builtin.fail:
|
||||
msg: "Variable {{ item }} is empty"
|
||||
when: "item in vars and not vars[item]"
|
||||
with_items: "{{ ipabackup_empty_var_checks }}"
|
||||
vars:
|
||||
@@ -18,74 +19,82 @@
|
||||
- ipabackup_firewalld_zone
|
||||
|
||||
- name: Set ipabackup_data if ipabackup_data is not set but ipabackup_online is
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipabackup_data: yes
|
||||
when: ipabackup_online | bool and not ipabackup_data | bool
|
||||
|
||||
- name: Fail if ipabackup_from_controller and ipabackup_to_controller are set
|
||||
fail: msg="ipabackup_from_controller and ipabackup_to_controller are set"
|
||||
ansible.builtin.fail:
|
||||
msg: "ipabackup_from_controller and ipabackup_to_controller are set"
|
||||
when: ipabackup_from_controller | bool and ipabackup_to_controller | bool
|
||||
|
||||
- name: Fail for given ipabackup_name if state is not copied, restored or absent
|
||||
fail: msg="ipabackup_name is given and state is not copied, restored or absent"
|
||||
ansible.builtin.fail:
|
||||
msg: "ipabackup_name is given and state is not copied, restored or absent"
|
||||
when: state is not defined or
|
||||
(state != "copied" and state != "restored" and state != "absent") and
|
||||
ipabackup_name is defined
|
||||
|
||||
- name: Get ipabackup_dir from IPA installation
|
||||
include_tasks: "{{ role_path }}/tasks/get_ipabackup_dir.yml"
|
||||
ansible.builtin.include_tasks: "{{ role_path }}/tasks/get_ipabackup_dir.yml"
|
||||
|
||||
- name: Backup IPA server
|
||||
include_tasks: "{{ role_path }}/tasks/backup.yml"
|
||||
ansible.builtin.include_tasks: "{{ role_path }}/tasks/backup.yml"
|
||||
when: state|default("present") == "present"
|
||||
|
||||
- name: Fail on missing ipabackup_name
|
||||
fail: msg="ipabackup_name is not set"
|
||||
ansible.builtin.fail:
|
||||
msg: "ipabackup_name is not set"
|
||||
when: (ipabackup_name is not defined or not ipabackup_name) and
|
||||
state is defined and
|
||||
(state == "copied" or state == "restored" or state == "absent")
|
||||
|
||||
- block:
|
||||
- name: Get all backup names for copy to controller
|
||||
when: state is defined and
|
||||
((state == "copied" and ipabackup_to_controller) or
|
||||
state == "absent") and
|
||||
ipabackup_name is defined and ipabackup_name == "all"
|
||||
block:
|
||||
- name: Get list of all backups on IPA server
|
||||
shell:
|
||||
ansible.builtin.shell:
|
||||
find . -name "ipa-full-*" -o -name "ipa-data-*" | cut -d"/" -f 2
|
||||
args:
|
||||
chdir: "{{ ipabackup_dir }}/"
|
||||
register: result_backup_find_backup_files
|
||||
|
||||
- name: Set ipabackup_names using backup list
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipabackup_names: "{{ result_backup_find_backup_files.stdout_lines }}"
|
||||
|
||||
when: state is defined and
|
||||
((state == "copied" and ipabackup_to_controller) or
|
||||
state == "absent") and
|
||||
ipabackup_name is defined and ipabackup_name == "all"
|
||||
|
||||
- block:
|
||||
- name: Set ipabackup_names from ipabackup_name
|
||||
when: ipabackup_names is not defined and ipabackup_name is defined
|
||||
block:
|
||||
- name: Fail on ipabackup_name all
|
||||
fail: msg="ipabackup_name can not be all in this case"
|
||||
ansible.builtin.fail:
|
||||
msg: "ipabackup_name can not be all in this case"
|
||||
when: ipabackup_name is defined and ipabackup_name == "all"
|
||||
|
||||
- name: Set ipabackup_names from ipabackup_name string
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipabackup_names: ["{{ ipabackup_name }}"]
|
||||
when: ipabackup_name | type_debug != "list"
|
||||
|
||||
- name: Set ipabackup_names from ipabackup_name list
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipabackup_names: "{{ ipabackup_name }}"
|
||||
when: ipabackup_name | type_debug == "list"
|
||||
when: ipabackup_names is not defined and ipabackup_name is defined
|
||||
|
||||
- name: Set empty ipabackup_names if ipabackup_name is not defined
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipabackup_names: []
|
||||
when: ipabackup_names is not defined and ipabackup_name is not defined
|
||||
|
||||
- block:
|
||||
- name: Process "{{ ipabackup_names }}"
|
||||
when: state is defined and
|
||||
((state == "copied" and ipabackup_to_controller) or state == "absent")
|
||||
block:
|
||||
- name: Copy backup from IPA server
|
||||
include_tasks: "{{ role_path }}/tasks/copy_backup_from_server.yml"
|
||||
ansible.builtin.include_tasks: "{{ role_path }}/tasks/copy_backup_from_server.yml"
|
||||
vars:
|
||||
ipabackup_item: "{{ main_item | basename }}"
|
||||
with_items:
|
||||
@@ -95,7 +104,7 @@
|
||||
when: state is defined and state == "copied"
|
||||
|
||||
- name: Remove backup from IPA server
|
||||
include_tasks: "{{ role_path }}/tasks/remove_backup_from_server.yml"
|
||||
ansible.builtin.include_tasks: "{{ role_path }}/tasks/remove_backup_from_server.yml"
|
||||
vars:
|
||||
ipabackup_item: "{{ main_item | basename }}"
|
||||
with_items:
|
||||
@@ -104,34 +113,32 @@
|
||||
loop_var: main_item
|
||||
when: state is defined and state == "absent"
|
||||
|
||||
when: state is defined and
|
||||
((state == "copied" and ipabackup_to_controller) or state == "absent")
|
||||
|
||||
# Fail with more than one entry in ipabackup_names for copy to sever and
|
||||
# restore.
|
||||
|
||||
- name: Fail to copy or restore more than one backup on the server
|
||||
fail: msg="Only one backup can be copied to the server or restored"
|
||||
ansible.builtin.fail:
|
||||
msg: "Only one backup can be copied to the server or restored"
|
||||
when: state is defined and (state == "copied" or state == "restored") and
|
||||
ipabackup_from_controller | bool and ipabackup_names | length != 1
|
||||
|
||||
# Use only first item in ipabackup_names for copy to server and for restore.
|
||||
|
||||
- block:
|
||||
- name: Copy backup to server
|
||||
include_tasks: "{{ role_path }}/tasks/copy_backup_to_server.yml"
|
||||
|
||||
- name: Restore IPA server after copy
|
||||
include_tasks: "{{ role_path }}/tasks/restore.yml"
|
||||
when: state|default("present") == "restored"
|
||||
|
||||
vars:
|
||||
ipabackup_name: "{{ ipabackup_names[0] }}"
|
||||
- name: Process "{{ ipabackup_names[0] }}"
|
||||
when: ipabackup_from_controller or
|
||||
(state|default("present") == "copied" and not ipabackup_to_controller)
|
||||
vars:
|
||||
ipabackup_name: "{{ ipabackup_names[0] }}"
|
||||
block:
|
||||
- name: Copy backup to server
|
||||
ansible.builtin.include_tasks: "{{ role_path }}/tasks/copy_backup_to_server.yml"
|
||||
|
||||
- name: Restore IPA server after copy
|
||||
ansible.builtin.include_tasks: "{{ role_path }}/tasks/restore.yml"
|
||||
when: state|default("present") == "restored"
|
||||
|
||||
- name: Restore IPA server
|
||||
include_tasks: "{{ role_path }}/tasks/restore.yml"
|
||||
ansible.builtin.include_tasks: "{{ role_path }}/tasks/restore.yml"
|
||||
vars:
|
||||
ipabackup_item: "{{ ipabackup_names[0] | basename }}"
|
||||
when: not ipabackup_from_controller and
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
- name: Remove backup "{{ ipabackup_item }}"
|
||||
file:
|
||||
ansible.builtin.file:
|
||||
path: "{{ ipabackup_dir }}/{{ ipabackup_item }}"
|
||||
state: absent
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
### VARIABLES
|
||||
|
||||
- name: Import variables specific to distribution
|
||||
include_vars: "{{ item }}"
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "{{ role_path }}/vars/{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_version'] }}.yml"
|
||||
- "{{ role_path }}/vars/{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_major_version'] }}.yml"
|
||||
@@ -21,30 +21,32 @@
|
||||
### GET SERVICES FROM BACKUP
|
||||
|
||||
- name: Stat backup on server
|
||||
stat:
|
||||
ansible.builtin.stat:
|
||||
path: "{{ ipabackup_dir }}/{{ ipabackup_item }}"
|
||||
register: result_backup_stat
|
||||
|
||||
- name: Fail on missing backup directory
|
||||
fail: msg="Unable to find backup {{ ipabackup_item }}"
|
||||
ansible.builtin.fail:
|
||||
msg: "Unable to find backup {{ ipabackup_item }}"
|
||||
when: result_backup_stat.stat.isdir is not defined
|
||||
|
||||
- name: Stat header file in backup "{{ ipabackup_item }}"
|
||||
stat:
|
||||
ansible.builtin.stat:
|
||||
path: "{{ ipabackup_dir }}/{{ ipabackup_item }}/header"
|
||||
register: result_backup_header_stat
|
||||
|
||||
- name: Fail on missing header file in backup
|
||||
fail: msg="Unable to find backup {{ ipabackup_item }} header file"
|
||||
ansible.builtin.fail:
|
||||
msg: "Unable to find backup {{ ipabackup_item }} header file"
|
||||
when: result_backup_header_stat.stat.isreg is not defined
|
||||
|
||||
- name: Get services from backup
|
||||
shell: >
|
||||
ansible.builtin.shell: >
|
||||
grep "^services = " "{{ ipabackup_dir }}/{{ ipabackup_item }}/header" | cut -d"=" -f2 | tr -d '[:space:]'
|
||||
register: result_services_grep
|
||||
|
||||
- name: Set ipabackup_services
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipabackup_services: "{{ result_services_grep.stdout.split(',') }}"
|
||||
ipabackup_service_dns: DNS
|
||||
ipabackup_service_adtrust: ADTRUST
|
||||
@@ -52,78 +54,78 @@
|
||||
|
||||
### INSTALL PACKAGES
|
||||
|
||||
- block:
|
||||
- name: Package installation
|
||||
when: ipabackup_install_packages | bool
|
||||
block:
|
||||
- name: Ensure that IPA server packages are installed
|
||||
package:
|
||||
ansible.builtin.package:
|
||||
name: "{{ ipaserver_packages }}"
|
||||
state: present
|
||||
|
||||
- name: Ensure that IPA server packages for dns are installed
|
||||
package:
|
||||
ansible.builtin.package:
|
||||
name: "{{ ipaserver_packages_dns }}"
|
||||
state: present
|
||||
when: ipabackup_service_dns in ipabackup_services
|
||||
|
||||
- name: Ensure that IPA server packages for adtrust are installed
|
||||
package:
|
||||
ansible.builtin.package:
|
||||
name: "{{ ipaserver_packages_adtrust }}"
|
||||
state: present
|
||||
when: ipabackup_service_adtrust in ipabackup_services
|
||||
|
||||
- name: Ensure that firewalld packages are installed
|
||||
package:
|
||||
ansible.builtin.package:
|
||||
name: "{{ ipaserver_packages_firewalld }}"
|
||||
state: present
|
||||
when: ipabackup_setup_firewalld | bool
|
||||
|
||||
when: ipabackup_install_packages | bool
|
||||
|
||||
### START FIREWALLD
|
||||
|
||||
- block:
|
||||
- name: Firewall configuration
|
||||
when: ipabackup_setup_firewalld | bool
|
||||
block:
|
||||
- name: Ensure that firewalld is running
|
||||
systemd:
|
||||
ansible.builtin.systemd:
|
||||
name: firewalld
|
||||
enabled: yes
|
||||
state: started
|
||||
|
||||
- name: Firewalld - Verify runtime zone "{{ ipabackup_firewalld_zone }}"
|
||||
shell: >
|
||||
ansible.builtin.shell: >
|
||||
firewall-cmd
|
||||
--info-zone="{{ ipabackup_firewalld_zone }}"
|
||||
>/dev/null
|
||||
when: ipabackup_firewalld_zone is defined
|
||||
|
||||
- name: Firewalld - Verify permanent zone "{{ ipabackup_firewalld_zone }}"
|
||||
shell: >
|
||||
ansible.builtin.shell: >
|
||||
firewall-cmd
|
||||
--permanent
|
||||
--info-zone="{{ ipabackup_firewalld_zone }}"
|
||||
>/dev/null
|
||||
when: ipabackup_firewalld_zone is defined
|
||||
|
||||
when: ipabackup_setup_firewalld | bool
|
||||
|
||||
### RESTORE
|
||||
|
||||
- name: Restore backup
|
||||
no_log: True
|
||||
shell: >
|
||||
ansible.builtin.shell: >
|
||||
ipa-restore
|
||||
{{ ipabackup_item }}
|
||||
--unattended
|
||||
{{ "--password="+ipabackup_password if ipabackup_password is defined else "" }}
|
||||
{{ "--password=" + ipabackup_password if ipabackup_password is defined else "" }}
|
||||
{{ "--data" if ipabackup_data | bool else "" }}
|
||||
{{ "--online" if ipabackup_online | bool else "" }}
|
||||
{{ "--instance="+ipabackup_instance if ipabackup_instance is defined else "" }}
|
||||
{{ "--backend="+ipabackup_backend if ipabackup_backend is defined else "" }}
|
||||
{{ "--instance=" + ipabackup_instance if ipabackup_instance is defined else "" }}
|
||||
{{ "--backend=" + ipabackup_backend if ipabackup_backend is defined else "" }}
|
||||
{{ "--no-logs" if ipabackup_no_logs | bool else "" }}
|
||||
{{ "--log-file="+ipabackup_log_file if ipabackup_log_file is defined else "" }}
|
||||
{{ "--log-file=" + ipabackup_log_file if ipabackup_log_file is defined else "" }}
|
||||
register: result_iparestore
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Report error for restore operation
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ result_iparestore.stderr }}"
|
||||
when: result_iparestore is failed
|
||||
failed_when: yes
|
||||
@@ -131,10 +133,10 @@
|
||||
### CONFIGURE FIREWALLD
|
||||
|
||||
- name: Configure firewalld
|
||||
command: >
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd
|
||||
--permanent
|
||||
{{ "--zone="+ipabackup_firewalld_zone if ipabackup_firewalld_zone is defined else "" }}
|
||||
{{ "--zone=" + ipabackup_firewalld_zone if ipabackup_firewalld_zone is defined else "" }}
|
||||
--add-service=freeipa-ldap
|
||||
--add-service=freeipa-ldaps
|
||||
{{ "--add-service=freeipa-trust" if ipabackup_service_adtrust in ipabackup_services else "" }}
|
||||
@@ -143,9 +145,9 @@
|
||||
when: ipabackup_setup_firewalld | bool
|
||||
|
||||
- name: Configure firewalld runtime
|
||||
command: >
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd
|
||||
{{ "--zone="+ipabackup_firewalld_zone if ipabackup_firewalld_zone is defined else "" }}
|
||||
{{ "--zone=" + ipabackup_firewalld_zone if ipabackup_firewalld_zone is defined else "" }}
|
||||
--add-service=freeipa-ldap
|
||||
--add-service=freeipa-ldaps
|
||||
{{ "--add-service=freeipa-trust" if ipabackup_service_adtrust in ipabackup_services else "" }}
|
||||
|
||||
@@ -183,6 +183,7 @@ Variable | Description | Required
|
||||
`ipaclient_no_ssh` | The bool value defines if OpenSSH client will be configured. `ipaclient_no_ssh` defaults to `no`. | no
|
||||
`ipaclient_no_sshd` | The bool value defines if OpenSSH server will be configured. `ipaclient_no_sshd` defaults to `no`. | no
|
||||
`ipaclient_no_sudo` | The bool value defines if SSSD will be configured as a data source for sudo. `ipaclient_no_sudo` defaults to `no`. | no
|
||||
`ipaclient_subid` | The bool value defines if SSSD will be configured as a data source for subid. `ipaclient_subid` defaults to `no`. | no
|
||||
`ipaclient_no_dns_sshfp` | The bool value defines if DNS SSHFP records will not be created automatically. `ipaclient_no_dns_sshfp` defaults to `no`. | no
|
||||
`ipaclient_force` | The bool value defines if settings will be forced even in the error case. `ipaclient_force` defaults to `no`. | no
|
||||
`ipaclient_force_ntpd` | The bool value defines if ntpd usage will be forced. This is not supported anymore and leads to a warning. `ipaclient_force_ntpd` defaults to `no`. | no
|
||||
|
||||
@@ -13,6 +13,7 @@ ipaclient_ssh_trust_dns: no
|
||||
ipaclient_no_ssh: no
|
||||
ipaclient_no_sshd: no
|
||||
ipaclient_no_sudo: no
|
||||
ipaclient_subid: no
|
||||
ipaclient_no_dns_sshfp: no
|
||||
ipaclient_force: no
|
||||
ipaclient_force_ntpd: no
|
||||
|
||||
@@ -55,6 +55,10 @@ options:
|
||||
type: bool
|
||||
required: no
|
||||
default: no
|
||||
krb_name:
|
||||
description: The krb5 config file name
|
||||
type: str
|
||||
required: yes
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
'''
|
||||
@@ -65,6 +69,7 @@ EXAMPLES = '''
|
||||
servers: ["server1.example.com","server2.example.com"]
|
||||
domain: example.com
|
||||
hostname: client1.example.com
|
||||
krb_name: /tmp/tmpkrb5.conf
|
||||
register: result_ipaclient_api
|
||||
'''
|
||||
|
||||
@@ -99,6 +104,7 @@ def main():
|
||||
realm=dict(required=True, type='str'),
|
||||
hostname=dict(required=True, type='str'),
|
||||
debug=dict(required=False, type='bool', default="false"),
|
||||
krb_name=dict(required=True, type='str'),
|
||||
),
|
||||
supports_check_mode=False,
|
||||
)
|
||||
@@ -110,9 +116,11 @@ def main():
|
||||
realm = module.params.get('realm')
|
||||
hostname = module.params.get('hostname')
|
||||
debug = module.params.get('debug')
|
||||
krb_name = module.params.get('krb_name')
|
||||
|
||||
host_principal = 'host/%s@%s' % (hostname, realm)
|
||||
os.environ['KRB5CCNAME'] = paths.IPA_DNS_CCACHE
|
||||
os.environ['KRB5_CONFIG'] = krb_name
|
||||
|
||||
ca_certs = x509.load_certificate_list_from_file(paths.IPA_CA_CRT)
|
||||
if 40500 <= NUM_VERSION < 40590:
|
||||
@@ -228,7 +236,8 @@ def main():
|
||||
except Exception as e:
|
||||
logger.debug("config_show failed %s", e, exc_info=True)
|
||||
module.fail_json(
|
||||
"Failed to retrieve CA certificate subject base: {}".format(e),
|
||||
"Failed to retrieve CA certificate subject base: "
|
||||
"{0}".format(e),
|
||||
rval=CLIENT_INSTALL_ERROR)
|
||||
else:
|
||||
subject_base = str(DN(config['ipacertificatesubjectbase'][0]))
|
||||
|
||||
@@ -266,10 +266,8 @@ def unconfigure_dns_resolver(fstore=None):
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
nameservers=dict(type="list", elements="str", aliases=["cn"],
|
||||
required=False),
|
||||
searchdomains=dict(type="list", elements="str", aliases=["cn"],
|
||||
required=False),
|
||||
nameservers=dict(type="list", elements="str", required=False),
|
||||
searchdomains=dict(type="list", elements="str", required=False),
|
||||
state=dict(type="str", default="present",
|
||||
choices=["present", "absent"]),
|
||||
),
|
||||
|
||||
@@ -54,6 +54,10 @@ options:
|
||||
the host entry will not be changed on the server
|
||||
type: bool
|
||||
required: yes
|
||||
krb_name:
|
||||
description: The krb5 config file name
|
||||
type: str
|
||||
required: yes
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
'''
|
||||
@@ -65,6 +69,7 @@ EXAMPLES = '''
|
||||
realm: EXAMPLE.COM
|
||||
basedn: dc=example,dc=com
|
||||
allow_repair: yes
|
||||
krb_name: /tmp/tmpkrb5.conf
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
@@ -87,6 +92,7 @@ def main():
|
||||
realm=dict(required=True, type='str'),
|
||||
basedn=dict(required=True, type='str'),
|
||||
allow_repair=dict(required=True, type='bool'),
|
||||
krb_name=dict(required=True, type='str'),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -98,6 +104,8 @@ def main():
|
||||
realm = module.params.get('realm')
|
||||
basedn = module.params.get('basedn')
|
||||
allow_repair = module.params.get('allow_repair')
|
||||
krb_name = module.params.get('krb_name')
|
||||
os.environ['KRB5_CONFIG'] = krb_name
|
||||
|
||||
env = {'PATH': SECURE_PATH}
|
||||
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
|
||||
|
||||
@@ -46,10 +46,6 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
required: yes
|
||||
domain:
|
||||
description: Primary DNS domain of the IPA deployment
|
||||
type: str
|
||||
required: yes
|
||||
realm:
|
||||
description: Kerberos realm name of the IPA deployment
|
||||
type: str
|
||||
@@ -58,10 +54,6 @@ options:
|
||||
description: Fully qualified name of this host
|
||||
type: str
|
||||
required: yes
|
||||
kdc:
|
||||
description: The name or address of the host running the KDC
|
||||
type: str
|
||||
required: yes
|
||||
basedn:
|
||||
description: The basedn of the IPA server (of the form dc=example,dc=com)
|
||||
type: str
|
||||
@@ -102,6 +94,10 @@ options:
|
||||
description: Turn on extra debugging
|
||||
type: bool
|
||||
required: no
|
||||
krb_name:
|
||||
description: The krb5 config file name
|
||||
type: str
|
||||
required: yes
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
'''
|
||||
@@ -111,27 +107,25 @@ EXAMPLES = '''
|
||||
- name: Join IPA in force mode with maximum 5 kinit attempts
|
||||
ipaclient_join:
|
||||
servers: ["server1.example.com","server2.example.com"]
|
||||
domain: example.com
|
||||
realm: EXAMPLE.COM
|
||||
kdc: server1.example.com
|
||||
basedn: dc=example,dc=com
|
||||
hostname: client1.example.com
|
||||
principal: admin
|
||||
password: MySecretPassword
|
||||
force_join: yes
|
||||
kinit_attempts: 5
|
||||
krb_name: /tmp/tmpkrb5.conf
|
||||
|
||||
# Join IPA to get the keytab using ipadiscovery return values
|
||||
- name: Join IPA
|
||||
ipaclient_join:
|
||||
servers: "{{ ipadiscovery.servers }}"
|
||||
domain: "{{ ipadiscovery.domain }}"
|
||||
realm: "{{ ipadiscovery.realm }}"
|
||||
kdc: "{{ ipadiscovery.kdc }}"
|
||||
basedn: "{{ ipadiscovery.basedn }}"
|
||||
hostname: "{{ ipadiscovery.hostname }}"
|
||||
principal: admin
|
||||
password: MySecretPassword
|
||||
krb_name: /tmp/tmpkrb5.conf
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
@@ -147,9 +141,9 @@ import tempfile
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
setup_logging, check_imports,
|
||||
SECURE_PATH, sysrestore, paths, options, configure_krb5_conf,
|
||||
realm_to_suffix, kinit_keytab, GSSError, kinit_password, NUM_VERSION,
|
||||
get_ca_cert, get_ca_certs, errors, run
|
||||
SECURE_PATH, sysrestore, paths, options, realm_to_suffix, kinit_keytab,
|
||||
GSSError, kinit_password, NUM_VERSION, get_ca_cert, get_ca_certs, errors,
|
||||
run
|
||||
)
|
||||
|
||||
|
||||
@@ -157,10 +151,8 @@ def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
servers=dict(required=True, type='list', elements='str'),
|
||||
domain=dict(required=True, type='str'),
|
||||
realm=dict(required=True, type='str'),
|
||||
hostname=dict(required=True, type='str'),
|
||||
kdc=dict(required=True, type='str'),
|
||||
basedn=dict(required=True, type='str'),
|
||||
principal=dict(required=False, type='str'),
|
||||
password=dict(required=False, type='str', no_log=True),
|
||||
@@ -170,6 +162,7 @@ def main():
|
||||
force_join=dict(required=False, type='bool'),
|
||||
kinit_attempts=dict(required=False, type='int', default=5),
|
||||
debug=dict(required=False, type='bool'),
|
||||
krb_name=dict(required=True, type='str'),
|
||||
),
|
||||
supports_check_mode=False,
|
||||
)
|
||||
@@ -179,11 +172,9 @@ def main():
|
||||
setup_logging()
|
||||
|
||||
servers = module.params.get('servers')
|
||||
domain = module.params.get('domain')
|
||||
realm = module.params.get('realm')
|
||||
hostname = module.params.get('hostname')
|
||||
basedn = module.params.get('basedn')
|
||||
kdc = module.params.get('kdc')
|
||||
force_join = module.params.get('force_join')
|
||||
principal = module.params.get('principal')
|
||||
password = module.params.get('password')
|
||||
@@ -192,6 +183,7 @@ def main():
|
||||
ca_cert_file = module.params.get('ca_cert_file')
|
||||
kinit_attempts = module.params.get('kinit_attempts')
|
||||
debug = module.params.get('debug')
|
||||
krb_name = module.params.get('krb_name')
|
||||
|
||||
if password is not None and keytab is not None:
|
||||
module.fail_json(msg="Password and keytab cannot be used together")
|
||||
@@ -199,12 +191,10 @@ def main():
|
||||
if password is None and admin_keytab is None:
|
||||
module.fail_json(msg="Password or admin_keytab is needed")
|
||||
|
||||
client_domain = hostname[hostname.find(".") + 1:]
|
||||
nolog = tuple()
|
||||
env = {'PATH': SECURE_PATH}
|
||||
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
|
||||
host_principal = 'host/%s@%s' % (hostname, realm)
|
||||
sssd = True
|
||||
|
||||
options.ca_cert_file = ca_cert_file
|
||||
options.principal = principal
|
||||
@@ -215,19 +205,6 @@ def main():
|
||||
changed = False
|
||||
already_joined = False
|
||||
try:
|
||||
(krb_fd, krb_name) = tempfile.mkstemp()
|
||||
os.close(krb_fd)
|
||||
configure_krb5_conf(
|
||||
cli_realm=realm,
|
||||
cli_domain=domain,
|
||||
cli_server=servers,
|
||||
cli_kdc=kdc,
|
||||
dnsok=False,
|
||||
filename=krb_name,
|
||||
client_domain=client_domain,
|
||||
client_hostname=hostname,
|
||||
configure_sssd=sssd,
|
||||
force=False)
|
||||
env['KRB5_CONFIG'] = krb_name
|
||||
ccache_dir = tempfile.mkdtemp(prefix='krbcc')
|
||||
ccache_name = os.path.join(ccache_dir, 'ccache')
|
||||
@@ -264,7 +241,7 @@ def main():
|
||||
config=krb_name)
|
||||
except RuntimeError as e:
|
||||
module.fail_json(
|
||||
msg="Kerberos authentication failed: {}".format(e))
|
||||
msg="Kerberos authentication failed: {0}".format(e))
|
||||
|
||||
elif keytab:
|
||||
join_args.append("-f")
|
||||
@@ -277,10 +254,10 @@ def main():
|
||||
attempts=kinit_attempts)
|
||||
except GSSError as e:
|
||||
module.fail_json(
|
||||
msg="Kerberos authentication failed: {}".format(e))
|
||||
msg="Kerberos authentication failed: {0}".format(e))
|
||||
else:
|
||||
module.fail_json(
|
||||
msg="Keytab file could not be found: {}".format(keytab))
|
||||
msg="Keytab file could not be found: {0}".format(keytab))
|
||||
|
||||
elif password:
|
||||
join_args.append("-w")
|
||||
@@ -336,27 +313,17 @@ def main():
|
||||
paths.IPA_DNS_CCACHE,
|
||||
config=krb_name,
|
||||
attempts=kinit_attempts)
|
||||
env['KRB5CCNAME'] = os.environ['KRB5CCNAME'] = paths.IPA_DNS_CCACHE
|
||||
except GSSError as e:
|
||||
# failure to get ticket makes it impossible to login and
|
||||
# bind from sssd to LDAP, abort installation
|
||||
module.fail_json(msg="Failed to obtain host TGT: %s" % e)
|
||||
|
||||
finally:
|
||||
try:
|
||||
os.remove(krb_name)
|
||||
except OSError:
|
||||
module.fail_json(msg="Could not remove %s" % krb_name)
|
||||
if ccache_dir is not None:
|
||||
try:
|
||||
os.rmdir(ccache_dir)
|
||||
except OSError:
|
||||
pass
|
||||
if os.path.exists(krb_name + ".ipabkp"):
|
||||
try:
|
||||
os.remove(krb_name + ".ipabkp")
|
||||
except OSError:
|
||||
module.fail_json(msg="Could not remove %s.ipabkp" % krb_name)
|
||||
|
||||
module.exit_json(changed=changed,
|
||||
already_joined=already_joined)
|
||||
|
||||
123
roles/ipaclient/library/ipaclient_setup_certmonger.py
Normal file
123
roles/ipaclient/library/ipaclient_setup_certmonger.py
Normal file
@@ -0,0 +1,123 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Based on ipa-client-install code
|
||||
#
|
||||
# Copyright (C) 2017-2022 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: ipaclient_setup_certmonger
|
||||
short_description: Setup certmonger for IPA client
|
||||
description: Setup certmonger for IPA client
|
||||
options:
|
||||
realm:
|
||||
description: Kerberos realm name of the IPA deployment
|
||||
type: str
|
||||
required: yes
|
||||
hostname:
|
||||
description: Fully qualified name of this host
|
||||
type: str
|
||||
required: yes
|
||||
subject_base:
|
||||
description: |
|
||||
The certificate subject base (default O=<realm-name>).
|
||||
RDNs are in LDAP order (most specific RDN first).
|
||||
type: str
|
||||
required: yes
|
||||
ca_enabled:
|
||||
description: Whether the Certificate Authority is enabled or not
|
||||
type: bool
|
||||
required: yes
|
||||
request_cert:
|
||||
description: Request certificate for the machine
|
||||
type: bool
|
||||
required: yes
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Setup certmonger for IPA client
|
||||
ipaclient_setup_certmonger:
|
||||
realm: EXAMPLE.COM
|
||||
hostname: client1.example.com
|
||||
subject_base: O=EXAMPLE.COM
|
||||
ca_enabled: true
|
||||
request_cert: false
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
setup_logging, check_imports,
|
||||
options, sysrestore, paths, ScriptError, configure_certmonger
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
realm=dict(required=True, type='str'),
|
||||
hostname=dict(required=True, type='str'),
|
||||
subject_base=dict(required=True, type='str'),
|
||||
ca_enabled=dict(required=True, type='bool'),
|
||||
request_cert=dict(required=True, type='bool'),
|
||||
),
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
module._ansible_debug = True
|
||||
check_imports(module)
|
||||
setup_logging()
|
||||
|
||||
cli_realm = module.params.get('realm')
|
||||
hostname = module.params.get('hostname')
|
||||
subject_base = module.params.get('subject_base')
|
||||
ca_enabled = module.params.get('ca_enabled')
|
||||
|
||||
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
|
||||
|
||||
options.request_cert = module.params.get('request_cert')
|
||||
options.hostname = hostname
|
||||
|
||||
try:
|
||||
configure_certmonger(fstore, subject_base, cli_realm, hostname,
|
||||
options, ca_enabled)
|
||||
|
||||
except ScriptError as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
module.exit_json(changed=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -125,6 +125,10 @@ options:
|
||||
description: Do not configure SSSD as data source for sudo
|
||||
type: bool
|
||||
required: no
|
||||
subid:
|
||||
description: Configure SSSD as data source for subid
|
||||
type: bool
|
||||
required: no
|
||||
fixed_primary:
|
||||
description: Configure sssd to use fixed server as primary IPA server
|
||||
type: bool
|
||||
@@ -148,6 +152,10 @@ options:
|
||||
The dist of nss_ldap or nss-pam-ldapd files if sssd is disabled
|
||||
required: yes
|
||||
type: dict
|
||||
krb_name:
|
||||
description: The krb5 config file name
|
||||
type: str
|
||||
required: yes
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
'''
|
||||
@@ -163,6 +171,7 @@ EXAMPLES = '''
|
||||
subject_base: O=EXAMPLE.COM
|
||||
principal: admin
|
||||
ca_enabled: yes
|
||||
krb_name: /tmp/tmpkrb5.conf
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
@@ -177,7 +186,7 @@ from ansible.module_utils.ansible_ipa_client import (
|
||||
options, sysrestore, paths, ansible_module_get_parsed_ip_addresses,
|
||||
api, errors, create_ipa_nssdb, ipautil, ScriptError, CLIENT_INSTALL_ERROR,
|
||||
get_certs_from_ldap, DN, certstore, x509, logger, certdb,
|
||||
CalledProcessError, tasks, client_dns, configure_certmonger, services,
|
||||
CalledProcessError, tasks, client_dns, services,
|
||||
update_ssh_keys, save_state, configure_ldap_conf, configure_nslcd_conf,
|
||||
configure_openldap_conf, hardcode_ldap_server, getargspec, NUM_VERSION,
|
||||
serialization
|
||||
@@ -208,11 +217,13 @@ def main():
|
||||
no_ssh=dict(required=False, type='bool'),
|
||||
no_sshd=dict(required=False, type='bool'),
|
||||
no_sudo=dict(required=False, type='bool'),
|
||||
subid=dict(required=False, type='bool'),
|
||||
fixed_primary=dict(required=False, type='bool'),
|
||||
permit=dict(required=False, type='bool'),
|
||||
no_krb5_offline_passwords=dict(required=False, type='bool'),
|
||||
no_dns_sshfp=dict(required=False, type='bool', default=False),
|
||||
nosssd_files=dict(required=True, type='dict'),
|
||||
krb_name=dict(required=True, type='str'),
|
||||
),
|
||||
supports_check_mode=False,
|
||||
)
|
||||
@@ -251,6 +262,7 @@ def main():
|
||||
options.conf_sshd = not options.no_sshd
|
||||
options.no_sudo = module.params.get('no_sudo')
|
||||
options.conf_sudo = not options.no_sudo
|
||||
options.subid = module.params.get('subid')
|
||||
options.primary = module.params.get('fixed_primary')
|
||||
options.permit = module.params.get('permit')
|
||||
options.no_krb5_offline_passwords = module.params.get(
|
||||
@@ -262,6 +274,8 @@ def main():
|
||||
options.sssd = not options.no_sssd
|
||||
options.no_ac = False
|
||||
nosssd_files = module.params.get('nosssd_files')
|
||||
krb_name = module.params.get('krb_name')
|
||||
os.environ['KRB5_CONFIG'] = krb_name
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
CCACHE_FILE = paths.IPA_DNS_CCACHE
|
||||
@@ -350,8 +364,6 @@ def main():
|
||||
|
||||
if not options.on_master:
|
||||
client_dns(cli_server[0], hostname, options)
|
||||
configure_certmonger(fstore, subject_base, cli_realm, hostname,
|
||||
options, ca_enabled)
|
||||
|
||||
if hasattr(paths, "SSH_CONFIG_DIR"):
|
||||
ssh_config_dir = paths.SSH_CONFIG_DIR
|
||||
@@ -430,19 +442,17 @@ def main():
|
||||
# Modify nsswitch/pam stack
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = getargspec(tasks.modify_nsswitch_pam_stack)
|
||||
the_options = {
|
||||
"sssd": options.sssd,
|
||||
"mkhomedir": options.mkhomedir,
|
||||
"statestore": statestore,
|
||||
}
|
||||
if "sudo" in argspec.args:
|
||||
tasks.modify_nsswitch_pam_stack(
|
||||
sssd=options.sssd,
|
||||
mkhomedir=options.mkhomedir,
|
||||
statestore=statestore,
|
||||
sudo=options.conf_sudo
|
||||
)
|
||||
else:
|
||||
tasks.modify_nsswitch_pam_stack(
|
||||
sssd=options.sssd,
|
||||
mkhomedir=options.mkhomedir,
|
||||
statestore=statestore
|
||||
)
|
||||
the_options["sudo"] = options.conf_sudo
|
||||
if "subid" in argspec.args:
|
||||
the_options["subid"] = options.subid
|
||||
|
||||
tasks.modify_nsswitch_pam_stack(**the_options)
|
||||
|
||||
if hasattr(paths, "AUTHSELECT") and paths.AUTHSELECT is not None:
|
||||
# authselect is used
|
||||
|
||||
163
roles/ipaclient/library/ipaclient_temp_krb5.py
Normal file
163
roles/ipaclient/library/ipaclient_temp_krb5.py
Normal file
@@ -0,0 +1,163 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Based on ipa-client-install code
|
||||
#
|
||||
# Copyright (C) 2017-2022 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: ipaclient_temp_krb5
|
||||
short_description:
|
||||
Create temporary krb5 configuration.
|
||||
description:
|
||||
Create temporary krb5 configuration for deferring the creation of the final
|
||||
krb5.conf on clients
|
||||
options:
|
||||
servers:
|
||||
description: Fully qualified name of IPA servers to enroll to
|
||||
type: list
|
||||
elements: str
|
||||
required: yes
|
||||
domain:
|
||||
description: Primary DNS domain of the IPA deployment
|
||||
type: str
|
||||
required: yes
|
||||
realm:
|
||||
description: Kerberos realm name of the IPA deployment
|
||||
type: str
|
||||
required: yes
|
||||
hostname:
|
||||
description: Fully qualified name of this host
|
||||
type: str
|
||||
required: yes
|
||||
kdc:
|
||||
description: The name or address of the host running the KDC
|
||||
type: str
|
||||
required: yes
|
||||
on_master:
|
||||
description: Whether the configuration is done on the master or not
|
||||
type: bool
|
||||
required: no
|
||||
default: no
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Test IPA with local keytab
|
||||
- name: Test IPA in force mode with maximum 5 kinit attempts
|
||||
ipaclient_test_keytab:
|
||||
servers: ["server1.example.com","server2.example.com"]
|
||||
domain: example.com
|
||||
realm: EXAMPLE.COM
|
||||
kdc: server1.example.com
|
||||
hostname: client1.example.com
|
||||
|
||||
# Test IPA with ipadiscovery return values
|
||||
- name: Join IPA
|
||||
ipaclient_test_keytab:
|
||||
servers: "{{ ipadiscovery.servers }}"
|
||||
domain: "{{ ipadiscovery.domain }}"
|
||||
realm: "{{ ipadiscovery.realm }}"
|
||||
kdc: "{{ ipadiscovery.kdc }}"
|
||||
hostname: "{{ ipadiscovery.hostname }}"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
krb_name:
|
||||
description: The krb5 config file name
|
||||
returned: always
|
||||
type: str
|
||||
'''
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
setup_logging, check_imports, configure_krb5_conf
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
servers=dict(required=True, type='list', elements='str'),
|
||||
domain=dict(required=True, type='str'),
|
||||
realm=dict(required=True, type='str'),
|
||||
hostname=dict(required=True, type='str'),
|
||||
kdc=dict(required=True, type='str'),
|
||||
on_master=dict(required=False, type='bool', default=False),
|
||||
),
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
module._ansible_debug = True
|
||||
check_imports(module)
|
||||
setup_logging()
|
||||
|
||||
servers = module.params.get('servers')
|
||||
domain = module.params.get('domain')
|
||||
realm = module.params.get('realm')
|
||||
hostname = module.params.get('hostname')
|
||||
kdc = module.params.get('kdc')
|
||||
client_domain = hostname[hostname.find(".") + 1:]
|
||||
|
||||
krb_name = None
|
||||
# Create temporary krb5 configuration
|
||||
try:
|
||||
(krb_fd, krb_name) = tempfile.mkstemp()
|
||||
os.close(krb_fd)
|
||||
configure_krb5_conf(
|
||||
cli_realm=realm,
|
||||
cli_domain=domain,
|
||||
cli_server=servers,
|
||||
cli_kdc=kdc,
|
||||
dnsok=False,
|
||||
filename=krb_name,
|
||||
client_domain=client_domain,
|
||||
client_hostname=hostname,
|
||||
configure_sssd=True,
|
||||
force=False)
|
||||
except Exception as ex:
|
||||
if krb_name:
|
||||
try:
|
||||
os.remove(krb_name)
|
||||
except OSError:
|
||||
module.fail_json(msg="Could not remove %s" % krb_name)
|
||||
module.fail_json(
|
||||
msg="Failed to create temporary krb5 configuration: %s" % str(ex))
|
||||
|
||||
module.exit_json(changed=False,
|
||||
krb_name=krb_name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -432,7 +432,7 @@ def main():
|
||||
if options.ca_cert_files is not None:
|
||||
for value in options.ca_cert_files:
|
||||
if not isinstance(value, list):
|
||||
raise ValueError("Expected list, got {!r}".format(value))
|
||||
raise ValueError("Expected list, got {0!r}".format(value))
|
||||
# this is what init() does
|
||||
value = value[-1]
|
||||
if not os.path.exists(value):
|
||||
@@ -575,13 +575,13 @@ def main():
|
||||
hostname_source = "Machine's FQDN"
|
||||
if hostname != hostname.lower():
|
||||
raise ScriptError(
|
||||
"Invalid hostname '{}', must be lower-case.".format(hostname),
|
||||
"Invalid hostname '{0}', must be lower-case.".format(hostname),
|
||||
rval=CLIENT_INSTALL_ERROR
|
||||
)
|
||||
|
||||
if hostname in ('localhost', 'localhost.localdomain'):
|
||||
raise ScriptError(
|
||||
"Invalid hostname, '{}' must not be used.".format(hostname),
|
||||
"Invalid hostname, '{0}' must not be used.".format(hostname),
|
||||
rval=CLIENT_INSTALL_ERROR)
|
||||
|
||||
if hasattr(constants, "MAXHOSTNAMELEN"):
|
||||
@@ -589,7 +589,7 @@ def main():
|
||||
validate_hostname(hostname, maxlen=constants.MAXHOSTNAMELEN)
|
||||
except ValueError as e:
|
||||
raise ScriptError(
|
||||
'invalid hostname: {}'.format(e),
|
||||
'invalid hostname: {0}'.format(e),
|
||||
rval=CLIENT_INSTALL_ERROR)
|
||||
|
||||
if hasattr(tasks, "is_nosssd_supported"):
|
||||
@@ -695,7 +695,7 @@ def main():
|
||||
rval=CLIENT_INSTALL_ERROR)
|
||||
if ret == ipadiscovery.NOT_FQDN:
|
||||
raise ScriptError(
|
||||
"{} is not a fully-qualified hostname".format(hostname),
|
||||
"{0} is not a fully-qualified hostname".format(hostname),
|
||||
rval=CLIENT_INSTALL_ERROR)
|
||||
if ret in (ipadiscovery.NO_LDAP_SERVER, ipadiscovery.NOT_IPA_SERVER) \
|
||||
or not ds.domain:
|
||||
|
||||
@@ -159,11 +159,29 @@ def main():
|
||||
ca_crt_exists = os.path.exists(paths.IPA_CA_CRT)
|
||||
env = {'PATH': SECURE_PATH, 'KRB5CCNAME': paths.IPA_DNS_CCACHE}
|
||||
|
||||
# First try: Validate krb5 keytab with system krb5 configuraiton
|
||||
# First try: Validate with temporary test krb5.conf that forces
|
||||
# 1) no DNS lookups and
|
||||
# 2) to load /etc/krb5.conf:
|
||||
#
|
||||
# [libdefaults]
|
||||
# dns_lookup_realm = false
|
||||
# dns_lookup_kdc = false
|
||||
# include /etc/krb5.conf
|
||||
#
|
||||
try:
|
||||
(krb_fd, krb_name) = tempfile.mkstemp()
|
||||
os.close(krb_fd)
|
||||
content = "\n".join([
|
||||
"[libdefaults]",
|
||||
"dns_lookup_realm = false",
|
||||
"dns_lookup_kdc = false",
|
||||
"include /etc/krb5.conf"
|
||||
])
|
||||
with open(krb_name, "w") as outf:
|
||||
outf.write(content)
|
||||
kinit_keytab(host_principal, paths.KRB5_KEYTAB,
|
||||
paths.IPA_DNS_CCACHE,
|
||||
config=paths.KRB5_CONF,
|
||||
config=krb_name,
|
||||
attempts=kinit_attempts)
|
||||
krb5_keytab_ok = True
|
||||
krb5_conf_ok = True
|
||||
@@ -177,6 +195,11 @@ def main():
|
||||
pass
|
||||
except GSSError:
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
os.remove(krb_name)
|
||||
except OSError:
|
||||
module.fail_json(msg="Could not remove %s" % krb_name)
|
||||
|
||||
# Second try: Validate krb5 keytab with temporary krb5
|
||||
# configuration
|
||||
@@ -221,6 +244,12 @@ def main():
|
||||
os.remove(krb_name)
|
||||
except OSError:
|
||||
module.fail_json(msg="Could not remove %s" % krb_name)
|
||||
if os.path.exists(krb_name + ".ipabkp"):
|
||||
try:
|
||||
os.remove(krb_name + ".ipabkp")
|
||||
except OSError:
|
||||
module.fail_json(
|
||||
msg="Could not remove %s.ipabkp" % krb_name)
|
||||
|
||||
module.exit_json(changed=False,
|
||||
krb5_keytab_ok=krb5_keytab_ok,
|
||||
|
||||
@@ -6,15 +6,15 @@ galaxy_info:
|
||||
description: A role to join a machine to an IPA domain
|
||||
company: Red Hat, Inc
|
||||
license: GPLv3
|
||||
min_ansible_version: 2.8
|
||||
min_ansible_version: "2.8"
|
||||
platforms:
|
||||
- name: Fedora
|
||||
versions:
|
||||
- all
|
||||
- name: EL
|
||||
versions:
|
||||
- 7
|
||||
- 8
|
||||
- "7"
|
||||
- "8"
|
||||
galaxy_tags:
|
||||
- identity
|
||||
- ipa
|
||||
|
||||
@@ -2,40 +2,45 @@
|
||||
# tasks file for ipaclient
|
||||
|
||||
- name: Install - Ensure that IPA client packages are installed
|
||||
package:
|
||||
ansible.builtin.package:
|
||||
name: "{{ ipaclient_packages }}"
|
||||
state: present
|
||||
when: ipaclient_install_packages | bool
|
||||
|
||||
- name: Install - Set ipaclient_servers
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipaclient_servers: "{{ groups['ipaservers'] | list }}"
|
||||
when: groups.ipaservers is defined and ipaclient_servers is not defined
|
||||
|
||||
- name: Install - Set ipaclient_servers from cluster inventory
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipaclient_servers: "{{ groups['ipaserver'] | list }}"
|
||||
when: ipaclient_no_dns_lookup | bool and groups.ipaserver is defined and
|
||||
ipaclient_servers is not defined
|
||||
|
||||
- name: Install - Check that either password or keytab is set
|
||||
fail: msg="ipaadmin_password and ipaadmin_keytab cannot be used together"
|
||||
ansible.builtin.fail:
|
||||
msg: "ipaadmin_password and ipaadmin_keytab cannot be used together"
|
||||
when: ipaadmin_keytab is defined and ipaadmin_password is defined
|
||||
|
||||
- name: Install - Set default principal if no keytab is given
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipaadmin_principal: admin
|
||||
when: ipaadmin_principal is undefined and ipaclient_keytab is undefined
|
||||
|
||||
- name: Install - Configure DNS resolver Block
|
||||
- name: Install - DNS resolver configuration
|
||||
when: ipaclient_configure_dns_resolver | bool
|
||||
and not ipaclient_on_master | bool
|
||||
block:
|
||||
|
||||
- name: Install - Fail on missing ipaclient_domain and ipaserver_domain
|
||||
fail: msg="ipaclient_domain or ipaserver_domain is required for ipaclient_configure_dns_resolver"
|
||||
ansible.builtin.fail:
|
||||
msg: "ipaclient_domain or ipaserver_domain is required for ipaclient_configure_dns_resolver"
|
||||
when: ipaserver_domain is not defined and ipaclient_domain is not defined
|
||||
|
||||
- name: Install - Fail on missing ipaclient_servers
|
||||
fail: msg="ipaclient_dns_servers is required for ipaclient_configure_dns_resolver"
|
||||
ansible.builtin.fail:
|
||||
msg: "ipaclient_dns_servers is required for ipaclient_configure_dns_resolver"
|
||||
when: ipaclient_dns_servers is not defined
|
||||
|
||||
- name: Install - Configure DNS resolver
|
||||
@@ -44,9 +49,6 @@
|
||||
searchdomains: "{{ ipaserver_domain | default(ipaclient_domain) }}"
|
||||
state: present
|
||||
|
||||
when: ipaclient_configure_dns_resolver | bool
|
||||
and not ipaclient_on_master | bool
|
||||
|
||||
- name: Install - IPA client test
|
||||
ipaclient_test:
|
||||
### basic ###
|
||||
@@ -72,9 +74,13 @@
|
||||
| default(ipasssd_enable_dns_updates) }}"
|
||||
register: result_ipaclient_test
|
||||
|
||||
- block:
|
||||
- name: Install - Client deployment
|
||||
when: not ansible_check_mode and
|
||||
not (result_ipaclient_test.client_already_configured and
|
||||
not ipaclient_allow_repair | bool and not ipaclient_force_join | bool)
|
||||
block:
|
||||
- name: Install - Cleanup leftover ccache
|
||||
file:
|
||||
ansible.builtin.file:
|
||||
path: "/etc/ipa/.dns_ccache"
|
||||
state: absent
|
||||
|
||||
@@ -91,12 +97,12 @@
|
||||
domain: "{{ result_ipaclient_test.domain }}"
|
||||
|
||||
- name: Install - Make sure One-Time Password is enabled if it's already defined
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipaclient_use_otp: "yes"
|
||||
when: ipaclient_otp is defined
|
||||
|
||||
- name: Install - Disable One-Time Password for on_master
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipaclient_use_otp: "no"
|
||||
when: ipaclient_use_otp | bool and ipaclient_on_master | bool
|
||||
|
||||
@@ -112,7 +118,7 @@
|
||||
|
||||
- name: Install - Disable One-Time Password for client with working
|
||||
krb5.keytab
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipaclient_use_otp: "no"
|
||||
when: ipaclient_use_otp | bool and
|
||||
result_ipaclient_test_keytab.krb5_keytab_ok and
|
||||
@@ -125,10 +131,12 @@
|
||||
# to create a OneTime Password
|
||||
# If a keytab is specified in the hostent, then the hostent will be disabled
|
||||
# if ipaclient_use_otp is set.
|
||||
- block:
|
||||
- name: Install - Obtain OTP
|
||||
when: ipaclient_use_otp | bool and ipaclient_otp is not defined
|
||||
block:
|
||||
- name: Install - Keytab or password is required for getting otp
|
||||
ansible.builtin.fail:
|
||||
msg: Keytab or password is required for getting otp
|
||||
msg: "Keytab or password is required for getting otp"
|
||||
when: ipaadmin_keytab is undefined and ipaadmin_password is undefined
|
||||
|
||||
- name: Install - Create temporary file for keytab
|
||||
@@ -159,20 +167,17 @@
|
||||
delegate_to: "{{ result_ipaclient_test.servers[0] }}"
|
||||
|
||||
- name: Install - Report error for OTP generation
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ result_ipaclient_get_otp.msg }}"
|
||||
when: result_ipaclient_get_otp is failed
|
||||
failed_when: yes
|
||||
|
||||
- name: Install - Store the previously obtained OTP
|
||||
no_log: yes
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipaadmin_orig_password: "{{ ipaadmin_password | default(omit) }}"
|
||||
ipaadmin_password: "{{ result_ipaclient_get_otp.host.randompassword
|
||||
if result_ipaclient_get_otp.host is defined }}"
|
||||
|
||||
when: ipaclient_use_otp | bool and ipaclient_otp is not defined
|
||||
|
||||
always:
|
||||
- name: Install - Remove keytab temporary file
|
||||
ansible.builtin.file:
|
||||
@@ -183,12 +188,14 @@
|
||||
|
||||
- name: Store predefined OTP in admin_password
|
||||
no_log: yes
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipaadmin_orig_password: "{{ ipaadmin_password | default(omit) }}"
|
||||
ipaadmin_password: "{{ ipaclient_otp }}"
|
||||
when: ipaclient_otp is defined
|
||||
|
||||
- block:
|
||||
- name: Install - Check keytab, principal and keytab
|
||||
when: not ipaclient_on_master | bool
|
||||
block:
|
||||
# This block is executed only when
|
||||
# not (not ipaclient_on_master | bool and
|
||||
# not result_ipaclient_join.changed and
|
||||
@@ -198,19 +205,20 @@
|
||||
# result_ipaclient_join.already_joined)))
|
||||
|
||||
- name: Install - Check if principal and keytab are set
|
||||
fail: msg="Admin principal and client keytab cannot be used together"
|
||||
ansible.builtin.fail:
|
||||
msg: "Admin principal and client keytab cannot be used together"
|
||||
when: ipaadmin_principal is defined and ipaclient_keytab is defined
|
||||
|
||||
- name: Install - Check if one of password or keytabs are set
|
||||
fail: msg="At least one of password or keytabs must be specified"
|
||||
ansible.builtin.fail:
|
||||
msg: "At least one of password or keytabs must be specified"
|
||||
when: not result_ipaclient_test_keytab.krb5_keytab_ok
|
||||
and ipaadmin_password is undefined
|
||||
and ipaadmin_keytab is undefined
|
||||
and ipaclient_keytab is undefined
|
||||
when: not ipaclient_on_master | bool
|
||||
|
||||
- name: Install - Purge {{ result_ipaclient_test.realm }} from host keytab
|
||||
command: >
|
||||
- name: "Install - From host keytab, purge {{ result_ipaclient_test.realm }}"
|
||||
ansible.builtin.command: >
|
||||
/usr/sbin/ipa-rmkeytab
|
||||
-k /etc/krb5.keytab
|
||||
-r "{{ result_ipaclient_test.realm }}"
|
||||
@@ -231,12 +239,19 @@
|
||||
hostname: "{{ result_ipaclient_test.hostname }}"
|
||||
when: not ipaclient_on_master | bool
|
||||
|
||||
- name: Install - Join IPA
|
||||
ipaclient_join:
|
||||
- name: Install - Create temporary krb5 configuration
|
||||
ipaclient_temp_krb5:
|
||||
servers: "{{ result_ipaclient_test.servers }}"
|
||||
domain: "{{ result_ipaclient_test.domain }}"
|
||||
realm: "{{ result_ipaclient_test.realm }}"
|
||||
hostname: "{{ result_ipaclient_test.hostname }}"
|
||||
kdc: "{{ result_ipaclient_test.kdc }}"
|
||||
register: result_ipaclient_temp_krb5
|
||||
|
||||
- name: Install - Join IPA
|
||||
ipaclient_join:
|
||||
servers: "{{ result_ipaclient_test.servers }}"
|
||||
realm: "{{ result_ipaclient_test.realm }}"
|
||||
basedn: "{{ result_ipaclient_test.basedn }}"
|
||||
hostname: "{{ result_ipaclient_test.hostname }}"
|
||||
force_join: "{{ ipaclient_force_join | default(omit) }}"
|
||||
@@ -247,35 +262,44 @@
|
||||
admin_keytab: "{{ ipaadmin_keytab if ipaadmin_keytab is defined and not ipaclient_use_otp | bool else omit }}"
|
||||
# ca_cert_file: "{{ ipaclient_ca_cert_file | default(omit) }}"
|
||||
kinit_attempts: "{{ ipaclient_kinit_attempts | default(omit) }}"
|
||||
krb_name: "{{ result_ipaclient_temp_krb5.krb_name }}"
|
||||
register: result_ipaclient_join
|
||||
when: not ipaclient_on_master | bool and
|
||||
(not result_ipaclient_test_keytab.krb5_keytab_ok or
|
||||
ipaclient_force_join)
|
||||
|
||||
- block:
|
||||
- name: krb5 configuration not correct
|
||||
fail:
|
||||
msg: >
|
||||
The krb5 configuration is not correct, please enable allow_repair
|
||||
to fix this.
|
||||
when: not result_ipaclient_test_keytab.krb5_conf_ok
|
||||
- name: IPA test failed
|
||||
fail:
|
||||
msg: "The IPA test failed, please enable allow_repair to fix this."
|
||||
when: not result_ipaclient_test_keytab.ping_test_ok
|
||||
- name: ca.crt file is missing
|
||||
fail:
|
||||
msg: >
|
||||
The ca.crt file is missing, please enable allow_repair to fix this.
|
||||
when: not result_ipaclient_test_keytab.ca_crt_exists
|
||||
- name: Install - Allow repair checks
|
||||
when: not ipaclient_on_master | bool and
|
||||
not result_ipaclient_join.changed and
|
||||
not ipaclient_allow_repair | bool and
|
||||
(result_ipaclient_test_keytab.krb5_keytab_ok or
|
||||
(result_ipaclient_join.already_joined is defined and
|
||||
result_ipaclient_join.already_joined))
|
||||
block:
|
||||
- name: The krb5 configuration is not correct
|
||||
ansible.builtin.fail:
|
||||
msg: >
|
||||
The krb5 configuration is not correct, please enable allow_repair
|
||||
to fix this.
|
||||
when: not result_ipaclient_test_keytab.krb5_conf_ok
|
||||
- name: IPA test failed
|
||||
ansible.builtin.fail:
|
||||
msg: "The IPA test failed, please enable allow_repair to fix this."
|
||||
when: not result_ipaclient_test_keytab.ping_test_ok
|
||||
- name: Fail due to missing ca.crt file
|
||||
ansible.builtin.fail:
|
||||
msg: >
|
||||
The ca.crt file is missing, please enable allow_repair to fix this.
|
||||
when: not result_ipaclient_test_keytab.ca_crt_exists
|
||||
|
||||
- block:
|
||||
- name: Install - Configuration
|
||||
when: not (not ipaclient_on_master | bool and
|
||||
not result_ipaclient_join.changed and
|
||||
not ipaclient_allow_repair | bool
|
||||
and (result_ipaclient_test_keytab.krb5_keytab_ok
|
||||
or (result_ipaclient_join.already_joined is defined
|
||||
and result_ipaclient_join.already_joined)))
|
||||
block:
|
||||
- name: Install - Configure IPA default.conf
|
||||
ipaclient_ipa_conf:
|
||||
servers: "{{ result_ipaclient_test.servers }}"
|
||||
@@ -307,26 +331,13 @@
|
||||
"{{ ipassd_no_krb5_offline_passwords
|
||||
| default(ipasssd_no_krb5_offline_passwords) }}"
|
||||
|
||||
- name: Install - Configure krb5 for IPA realm
|
||||
ipaclient_setup_krb5:
|
||||
realm: "{{ result_ipaclient_test.realm }}"
|
||||
domain: "{{ result_ipaclient_test.domain }}"
|
||||
servers: "{{ result_ipaclient_test.servers }}"
|
||||
kdc: "{{ result_ipaclient_test.kdc }}"
|
||||
dnsok: "{{ result_ipaclient_test.dnsok }}"
|
||||
client_domain: "{{ result_ipaclient_test.client_domain }}"
|
||||
hostname: "{{ result_ipaclient_test.hostname }}"
|
||||
sssd: "{{ result_ipaclient_test.sssd }}"
|
||||
force: "{{ ipaclient_force }}"
|
||||
# on_master: "{{ ipaclient_on_master }}"
|
||||
when: not ipaclient_on_master | bool
|
||||
|
||||
- name: Install - IPA API calls for remaining enrollment parts
|
||||
ipaclient_api:
|
||||
servers: "{{ result_ipaclient_test.servers }}"
|
||||
realm: "{{ result_ipaclient_test.realm }}"
|
||||
hostname: "{{ result_ipaclient_test.hostname }}"
|
||||
# debug: yes
|
||||
krb_name: "{{ result_ipaclient_temp_krb5.krb_name }}"
|
||||
register: result_ipaclient_api
|
||||
|
||||
- name: Install - Fix IPA ca
|
||||
@@ -335,6 +346,7 @@
|
||||
realm: "{{ result_ipaclient_test.realm }}"
|
||||
basedn: "{{ result_ipaclient_test.basedn }}"
|
||||
allow_repair: "{{ ipaclient_allow_repair }}"
|
||||
krb_name: "{{ result_ipaclient_temp_krb5.krb_name }}"
|
||||
when: not ipaclient_on_master | bool and
|
||||
result_ipaclient_test_keytab.krb5_keytab_ok and
|
||||
not result_ipaclient_test_keytab.ca_crt_exists
|
||||
@@ -362,6 +374,7 @@
|
||||
no_ssh: "{{ ipaclient_no_ssh }}"
|
||||
no_sshd: "{{ ipaclient_no_sshd }}"
|
||||
no_sudo: "{{ ipaclient_no_sudo }}"
|
||||
subid: "{{ ipaclient_subid }}"
|
||||
fixed_primary: "{{ ipassd_fixed_primary
|
||||
| default(ipasssd_fixed_primary) }}"
|
||||
permit: "{{ ipassd_permit | default(ipasssd_permit) }}"
|
||||
@@ -370,6 +383,7 @@
|
||||
| default(ipasssd_no_krb5_offline_passwords) }}"
|
||||
no_dns_sshfp: "{{ ipaclient_no_dns_sshfp }}"
|
||||
nosssd_files: "{{ result_ipaclient_test.nosssd_files }}"
|
||||
krb_name: "{{ result_ipaclient_temp_krb5.krb_name }}"
|
||||
|
||||
- name: Install - Configure SSH and SSHD
|
||||
ipaclient_setup_ssh:
|
||||
@@ -397,25 +411,55 @@
|
||||
nisdomain: "{{ ipaclient_nisdomain | default(omit) }}"
|
||||
when: not ipaclient_no_nisdomain | bool
|
||||
|
||||
when: not (not ipaclient_on_master | bool and
|
||||
not result_ipaclient_join.changed and
|
||||
not ipaclient_allow_repair | bool
|
||||
and (result_ipaclient_test_keytab.krb5_keytab_ok
|
||||
or (result_ipaclient_join.already_joined is defined
|
||||
and result_ipaclient_join.already_joined)))
|
||||
- name: Remove temporary krb5.conf
|
||||
ansible.builtin.file:
|
||||
path: "{{ result_ipaclient_temp_krb5.krb_name }}"
|
||||
state: absent
|
||||
when: result_ipaclient_temp_krb5.krb_name is defined
|
||||
|
||||
when: not ansible_check_mode and
|
||||
not (result_ipaclient_test.client_already_configured and
|
||||
not ipaclient_allow_repair | bool and not ipaclient_force_join | bool)
|
||||
- name: Install - Configure krb5 for IPA realm
|
||||
ipaclient_setup_krb5:
|
||||
realm: "{{ result_ipaclient_test.realm }}"
|
||||
domain: "{{ result_ipaclient_test.domain }}"
|
||||
servers: "{{ result_ipaclient_test.servers }}"
|
||||
kdc: "{{ result_ipaclient_test.kdc }}"
|
||||
dnsok: "{{ result_ipaclient_test.dnsok }}"
|
||||
client_domain: "{{ result_ipaclient_test.client_domain }}"
|
||||
hostname: "{{ result_ipaclient_test.hostname }}"
|
||||
sssd: "{{ result_ipaclient_test.sssd }}"
|
||||
force: "{{ ipaclient_force }}"
|
||||
# on_master: "{{ ipaclient_on_master }}"
|
||||
when: not ipaclient_on_master | bool
|
||||
|
||||
- name: Install - Configure certmonger
|
||||
ipaclient_setup_certmonger:
|
||||
realm: "{{ result_ipaclient_test.realm }}"
|
||||
hostname: "{{ result_ipaclient_test.hostname }}"
|
||||
subject_base: "{{ result_ipaclient_api.subject_base }}"
|
||||
ca_enabled: "{{ result_ipaclient_api.ca_enabled }}"
|
||||
request_cert: "{{ ipaclient_request_cert }}"
|
||||
when: not ipaclient_on_master | bool
|
||||
|
||||
always:
|
||||
- name: Install - Restore original admin password if overwritten by OTP
|
||||
no_log: yes
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipaadmin_password: "{{ ipaadmin_orig_password }}"
|
||||
when: ipaclient_use_otp | bool and ipaadmin_orig_password is defined
|
||||
|
||||
- name: Cleanup leftover ccache
|
||||
file:
|
||||
ansible.builtin.file:
|
||||
path: "/etc/ipa/.dns_ccache"
|
||||
state: absent
|
||||
|
||||
- name: Remove temporary krb5.conf
|
||||
ansible.builtin.file:
|
||||
path: "{{ result_ipaclient_temp_krb5.krb_name }}"
|
||||
state: absent
|
||||
when: result_ipaclient_temp_krb5.krb_name is defined
|
||||
|
||||
- name: Remove temporary krb5.conf backup
|
||||
ansible.builtin.file:
|
||||
path: "{{ result_ipaclient_temp_krb5.krb_name }}.ipabkp"
|
||||
state: absent
|
||||
when: result_ipaclient_temp_krb5.krb_name is defined
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# tasks file for ipaclient
|
||||
|
||||
- name: Import variables specific to distribution
|
||||
include_vars: "{{ item }}"
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "{{ role_path }}/vars/{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_version'] }}.yml"
|
||||
- "{{ role_path }}/vars/{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_major_version'] }}.yml"
|
||||
@@ -17,9 +17,9 @@
|
||||
- "{{ role_path }}/vars/default.yml"
|
||||
|
||||
- name: Install IPA client
|
||||
include_tasks: install.yml
|
||||
ansible.builtin.include_tasks: install.yml
|
||||
when: state|default('present') == 'present'
|
||||
|
||||
- name: Uninstall IPA client
|
||||
include_tasks: uninstall.yml
|
||||
ansible.builtin.include_tasks: uninstall.yml
|
||||
when: state|default('present') == 'absent'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# tasks to uninstall IPA client
|
||||
|
||||
- name: Uninstall - Uninstall IPA client
|
||||
command: >
|
||||
ansible.builtin.command: >
|
||||
/usr/sbin/ipa-client-install
|
||||
--uninstall
|
||||
-U
|
||||
@@ -17,6 +17,6 @@
|
||||
when: ipaclient_cleanup_dns_resolver | bool
|
||||
|
||||
#- name: Remove IPA client package
|
||||
# package:
|
||||
# ansible.builtin.package:
|
||||
# name: "{{ ipaclient_packages }}"
|
||||
# state: absent
|
||||
|
||||
@@ -28,7 +28,7 @@ Supported Distributions
|
||||
|
||||
* RHEL/CentOS 7.6+
|
||||
* Fedora 26+
|
||||
* Ubuntu
|
||||
* Ubuntu 16.04 and 18.04
|
||||
|
||||
|
||||
Requirements
|
||||
@@ -114,6 +114,50 @@ Example playbook to setup the IPA client(s) using principal and password from in
|
||||
state: present
|
||||
```
|
||||
|
||||
Example inventory file to remove a replica from the domain:
|
||||
|
||||
```ini
|
||||
[ipareplicas]
|
||||
ipareplica1.example.com
|
||||
|
||||
[ipareplicas:vars]
|
||||
ipaadmin_password=MySecretPassword123
|
||||
ipareplica_remove_from_domain=true
|
||||
```
|
||||
|
||||
Example playbook to remove an IPA replica using admin passwords from the domain:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to remove IPA replica
|
||||
hosts: ipareplica
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- role: ipareplica
|
||||
state: absent
|
||||
```
|
||||
|
||||
The inventory will enable the removal of the replica (also a replica) from the domain. Additional options are needed if the removal of the replica is resulting in a topology disconnect or if the replica is the last that has a role.
|
||||
|
||||
To continue with the removal with a topology disconnect it is needed to set these parameters:
|
||||
|
||||
```ini
|
||||
ipareplica_ignore_topology_disconnect=true
|
||||
ipareplica_remove_on_server=ipareplica2.example.com
|
||||
```
|
||||
|
||||
To continue with the removal for a replica that is the last that has a role:
|
||||
|
||||
```ini
|
||||
ipareplica_ignore_last_of_role=true
|
||||
```
|
||||
|
||||
Be careful with enabling the `ipareplica_ignore_topology_disconnect` and especially `ipareplica_ignore_last_of_role`, the change can not be reverted easily.
|
||||
|
||||
The parameters `ipaserver_ignore_topology_disconnect`, `ipaserver_ignore_last_of_role`, `ipaserver_remove_on_server` and `ipaserver_remove_from_domain` can be used instead.
|
||||
|
||||
|
||||
Playbooks
|
||||
=========
|
||||
|
||||
@@ -200,6 +244,7 @@ Variable | Description | Required
|
||||
`ipaclient_no_ssh` | The bool value defines if OpenSSH client will be configured. (bool, default: false) | no
|
||||
`ipaclient_no_sshd` | The bool value defines if OpenSSH server will be configured. (bool, default: false) | no
|
||||
`ipaclient_no_sudo` | The bool value defines if SSSD will be configured as a data source for sudo. (bool, default: false) | no
|
||||
`ipaclient_subid` | The bool value defines if SSSD will be configured as a data source for subid. (bool, default: false) | no
|
||||
`ipaclient_no_dns_sshfp` | The bool value defines if DNS SSHFP records will not be created automatically. (bool, default: false) | no
|
||||
|
||||
Certificate system Variables
|
||||
@@ -254,6 +299,19 @@ Variable | Description | Required
|
||||
`ipareplica_setup_firewalld` | The value defines if the needed services will automatically be openen in the firewall managed by firewalld. (bool, default: true) | no
|
||||
`ipareplica_firewalld_zone` | The value defines the firewall zone that will be used. This needs to be an existing runtime and permanent zone. (string) | no
|
||||
|
||||
Undeploy Variables (`state`: absent)
|
||||
------------------------------------
|
||||
|
||||
These settings should only be used if the result is really wanted. The change might not be revertable easily.
|
||||
|
||||
Variable | Description | Required
|
||||
-------- | ----------- | --------
|
||||
`ipareplica_ignore_topology_disconnect` \| `ipaserver_ignore_topology_disconnect` | If enabled this enforces the removal of the replica even if it results in a topology disconnect. Be careful with this setting. (bool) | false
|
||||
`ipareplica_ignore_last_of_role` \| `ipaserver_ignore_last_of_role` | If enabled this enforces the removal of the replica even if the replica is the last with one that has a role. Be careful, this might not be revered easily. (bool) | false
|
||||
`ipareplica_remove_from_domain` \| `ipaserver_remove_from_domain` | This enables the removal of the replica from the domain additionally to the undeployment. (bool) | false
|
||||
`ipareplica_remove_on_server` \| `ipaserver_remove_on_server` | The value defines the replica in the domain that will to be used to remove the replica from the domain if `ipareplica_ignore_topology_disconnect` and `ipareplica_remove_from_domain` are enabled. Without the need to enable `ipareplica_ignore_topology_disconnect`, the value will be automatically detected using the replication agreements of the replica. (string) | false
|
||||
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ def main():
|
||||
# Print a warning if CA role is only installed on one server
|
||||
if len(ca_servers) == 1:
|
||||
msg = u'''
|
||||
WARNING: The CA service is only installed on one server ({}).
|
||||
WARNING: The CA service is only installed on one server ({0}).
|
||||
It is strongly recommended to install it on another server.
|
||||
Run ipa-ca-install(1) on another master to accomplish this.
|
||||
'''.format(ca_servers[0])
|
||||
|
||||
@@ -469,7 +469,7 @@ def main():
|
||||
env._finalize_core(**dict(constants.DEFAULT_CONFIG))
|
||||
|
||||
# pylint: disable=no-member
|
||||
xmlrpc_uri = 'https://{}/ipa/xml'.format(ipautil.format_netloc(env.host))
|
||||
xmlrpc_uri = 'https://{0}/ipa/xml'.format(ipautil.format_netloc(env.host))
|
||||
if hasattr(ipaldap, "realm_to_ldapi_uri"):
|
||||
realm_to_ldapi_uri = ipaldap.realm_to_ldapi_uri
|
||||
else:
|
||||
@@ -609,7 +609,7 @@ def main():
|
||||
ansible_log.debug("-- REMOTE_API --")
|
||||
|
||||
ldapuri = 'ldaps://%s' % ipautil.format_netloc(config.master_host_name)
|
||||
xmlrpc_uri = 'https://{}/ipa/xml'.format(
|
||||
xmlrpc_uri = 'https://{0}/ipa/xml'.format(
|
||||
ipautil.format_netloc(config.master_host_name))
|
||||
remote_api = create_api(mode=None)
|
||||
remote_api.bootstrap(in_server=True,
|
||||
|
||||
@@ -450,7 +450,7 @@ def main():
|
||||
if installer.ca_cert_files is not None:
|
||||
if not isinstance(installer.ca_cert_files, list):
|
||||
ansible_module.fail_json(
|
||||
msg="Expected list, got {!r}".format(installer.ca_cert_files))
|
||||
msg="Expected list, got {0!r}".format(installer.ca_cert_files))
|
||||
for cert in installer.ca_cert_files:
|
||||
if not os.path.exists(cert):
|
||||
ansible_module.fail_json(msg="'%s' does not exist" % cert)
|
||||
@@ -521,6 +521,11 @@ def main():
|
||||
ansible_module.fail_json(
|
||||
msg="NTP configuration cannot be updated during promotion")
|
||||
|
||||
# host_name an domain_name must be different at this point.
|
||||
if options.host_name.lower() == options.domain_name.lower():
|
||||
ansible_module.fail_json(
|
||||
msg="hostname cannot be the same as the domain name")
|
||||
|
||||
# done #
|
||||
|
||||
ansible_module.exit_json(
|
||||
|
||||
@@ -6,15 +6,15 @@ galaxy_info:
|
||||
description: A role to setup an IPA domain replica
|
||||
company: Red Hat, Inc
|
||||
license: GPLv3
|
||||
min_ansible_version: 2.8
|
||||
min_ansible_version: "2.8"
|
||||
platforms:
|
||||
- name: Fedora
|
||||
versions:
|
||||
- all
|
||||
- name: EL
|
||||
versions:
|
||||
- 7
|
||||
- 8
|
||||
- "7"
|
||||
- "8"
|
||||
galaxy_tags:
|
||||
- identity
|
||||
- ipa
|
||||
|
||||
@@ -334,7 +334,7 @@ def gen_env_boostrap_finalize_core(etc_ipa, default_config):
|
||||
def api_bootstrap_finalize(env):
|
||||
# pylint: disable=no-member
|
||||
xmlrpc_uri = \
|
||||
'https://{}/ipa/xml'.format(ipautil.format_netloc(env.host))
|
||||
'https://{0}/ipa/xml'.format(ipautil.format_netloc(env.host))
|
||||
api.bootstrap(in_server=True,
|
||||
context='installer',
|
||||
confdir=paths.ETC_IPA,
|
||||
@@ -479,7 +479,7 @@ def ansible_module_get_parsed_ip_addresses(ansible_module,
|
||||
|
||||
def gen_remote_api(master_host_name, etc_ipa):
|
||||
ldapuri = 'ldaps://%s' % ipautil.format_netloc(master_host_name)
|
||||
xmlrpc_uri = 'https://{}/ipa/xml'.format(
|
||||
xmlrpc_uri = 'https://{0}/ipa/xml'.format(
|
||||
ipautil.format_netloc(master_host_name))
|
||||
remote_api = create_api(mode=None)
|
||||
remote_api.bootstrap(in_server=True,
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
---
|
||||
# tasks file for ipareplica
|
||||
|
||||
- block:
|
||||
- name: Package installation
|
||||
when: ipareplica_install_packages | bool
|
||||
block:
|
||||
|
||||
- name: Install - Ensure IPA replica packages are installed
|
||||
package:
|
||||
ansible.builtin.package:
|
||||
name: "{{ ipareplica_packages }}"
|
||||
state: present
|
||||
|
||||
- name: Install - Ensure IPA replica packages for dns are installed
|
||||
package:
|
||||
ansible.builtin.package:
|
||||
name: "{{ ipareplica_packages_dns }}"
|
||||
state: present
|
||||
when: ipareplica_setup_dns | bool
|
||||
|
||||
- name: Install - Ensure IPA replica packages for adtrust are installed
|
||||
package:
|
||||
ansible.builtin.package:
|
||||
name: "{{ ipareplica_packages_adtrust }}"
|
||||
state: present
|
||||
when: ipareplica_setup_adtrust | bool
|
||||
|
||||
- name: Install - Ensure that firewall packages installed
|
||||
package:
|
||||
ansible.builtin.package:
|
||||
name: "{{ ipareplica_packages_firewalld }}"
|
||||
state: present
|
||||
when: ipareplica_setup_firewalld | bool
|
||||
|
||||
when: ipareplica_install_packages | bool
|
||||
|
||||
- block:
|
||||
- name: Firewall configuration
|
||||
when: ipareplica_setup_firewalld | bool
|
||||
block:
|
||||
- name: Firewalld service - Ensure that firewalld is running
|
||||
systemd:
|
||||
ansible.builtin.systemd:
|
||||
name: firewalld
|
||||
enabled: yes
|
||||
state: started
|
||||
|
||||
- name: Firewalld - Verify runtime zone "{{ ipareplica_firewalld_zone }}"
|
||||
shell: >
|
||||
ansible.builtin.shell: >
|
||||
firewall-cmd
|
||||
--info-zone="{{ ipareplica_firewalld_zone }}"
|
||||
>/dev/null
|
||||
when: ipareplica_firewalld_zone is defined
|
||||
|
||||
- name: Firewalld - Verify permanent zone "{{ ipareplica_firewalld_zone }}"
|
||||
shell: >
|
||||
ansible.builtin.shell: >
|
||||
firewall-cmd
|
||||
--permanent
|
||||
--info-zone="{{ ipareplica_firewalld_zone }}"
|
||||
>/dev/null
|
||||
when: ipareplica_firewalld_zone is defined
|
||||
|
||||
when: ipareplica_setup_firewalld | bool
|
||||
|
||||
- name: Install - Set ipareplica_servers
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipareplica_servers: "{{ groups['ipaservers'] | list }}"
|
||||
when: groups.ipaservers is defined and ipareplica_servers is not defined
|
||||
|
||||
- name: Install - Set default principal if no keytab is given
|
||||
set_fact:
|
||||
ansible.builtin.set_fact:
|
||||
ipaadmin_principal: admin
|
||||
when: ipaadmin_principal is undefined and ipaclient_keytab is undefined
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
domain: "{{ ipareplica_domain | default(ipaserver_domain) |
|
||||
default(omit) }}"
|
||||
servers: "{{ ipareplica_servers | default(omit) }}"
|
||||
realm: "{{ ipareplica_realm | default(ipaserver_realm) |default(omit) }}"
|
||||
realm: "{{ ipareplica_realm | default(ipaserver_realm) | default(omit) }}"
|
||||
hostname: "{{ ipareplica_hostname | default(ansible_facts['fqdn']) }}"
|
||||
ca_cert_files: "{{ ipareplica_ca_cert_files | default([]) }}"
|
||||
hidden_replica: "{{ ipareplica_hidden_replica }}"
|
||||
@@ -101,14 +101,18 @@
|
||||
no_dnssec_validation: "{{ ipareplica_no_dnssec_validation }}"
|
||||
register: result_ipareplica_test
|
||||
|
||||
- block:
|
||||
- name: Install - Deploy replica
|
||||
when: not ansible_check_mode and
|
||||
not (result_ipareplica_test.client_already_configured is defined or
|
||||
result_ipareplica_test.server_already_configured is defined)
|
||||
block:
|
||||
# This block is executed only when
|
||||
# not ansible_check_mode and
|
||||
# not (result_ipareplica_test.client_already_configured is defined or
|
||||
# result_ipareplica_test.server_already_configured is defined)
|
||||
|
||||
- name: Install - Setup client
|
||||
include_role:
|
||||
ansible.builtin.include_role:
|
||||
name: ipaclient
|
||||
vars:
|
||||
state: present
|
||||
@@ -120,7 +124,7 @@
|
||||
when: not result_ipareplica_test.client_enrolled
|
||||
|
||||
- name: Install - Configure firewalld
|
||||
command: >
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd
|
||||
--permanent
|
||||
--zone="{{ ipareplica_firewalld_zone if ipareplica_firewalld_zone is
|
||||
@@ -134,7 +138,7 @@
|
||||
when: ipareplica_setup_firewalld | bool
|
||||
|
||||
- name: Install - Configure firewalld runtime
|
||||
command: >
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd
|
||||
--zone="{{ ipareplica_firewalld_zone if ipareplica_firewalld_zone is
|
||||
defined else '' }}"
|
||||
@@ -222,8 +226,8 @@
|
||||
|
||||
- name: Install - Set dirman password
|
||||
no_log: yes
|
||||
set_fact:
|
||||
ipareplica_dirman_password:
|
||||
ansible.builtin.set_fact:
|
||||
__derived_dirman_password:
|
||||
"{{ result_ipareplica_master_password.password }}"
|
||||
|
||||
- name: Install - Setup certmonger
|
||||
@@ -251,10 +255,6 @@
|
||||
dirsrv_cert_files: "{{ ipareplica_dirsrv_cert_files | default([]) }}"
|
||||
### client ###
|
||||
force_join: "{{ ipaclient_force_join }}"
|
||||
### ad trust ###
|
||||
netbios_name: "{{ ipareplica_netbios_name | default(omit) }}"
|
||||
rid_base: "{{ ipareplica_rid_base | default(omit) }}"
|
||||
secondary_rid_base: "{{ ipareplica_secondary_rid_base | default(omit) }}"
|
||||
### additional ###
|
||||
server: "{{ result_ipareplica_test.server }}"
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
@@ -264,7 +264,7 @@
|
||||
_add_to_ipaservers: "{{ result_ipareplica_prepare._add_to_ipaservers }}"
|
||||
_ca_subject: "{{ result_ipareplica_prepare._ca_subject }}"
|
||||
_subject_base: "{{ result_ipareplica_prepare._subject_base }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
@@ -293,22 +293,18 @@
|
||||
dirsrv_cert_files: "{{ ipareplica_dirsrv_cert_files | default([]) }}"
|
||||
### client ###
|
||||
force_join: "{{ ipaclient_force_join }}"
|
||||
### ad trust ###
|
||||
netbios_name: "{{ ipareplica_netbios_name | default(omit) }}"
|
||||
rid_base: "{{ ipareplica_rid_base | default(omit) }}"
|
||||
secondary_rid_base: "{{ ipareplica_secondary_rid_base | default(omit) }}"
|
||||
### additional ###
|
||||
server: "{{ result_ipareplica_test.server }}"
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
installer_ccache: "{{ result_ipareplica_prepare.installer_ccache }}"
|
||||
_ca_enabled: "{{ result_ipareplica_prepare._ca_enabled }}"
|
||||
_dirsrv_pkcs12_info: "{{ result_ipareplica_prepare._dirsrv_pkcs12_info if result_ipareplica_prepare._dirsrv_pkcs12_info != None else omit }}"
|
||||
_dirsrv_pkcs12_info: "{{ result_ipareplica_prepare._dirsrv_pkcs12_info if result_ipareplica_prepare._dirsrv_pkcs12_info != None else omit }}"
|
||||
subject_base: "{{ result_ipareplica_prepare.subject_base }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
_add_to_ipaservers: "{{ result_ipareplica_prepare._add_to_ipaservers }}"
|
||||
_ca_subject: "{{ result_ipareplica_prepare._ca_subject }}"
|
||||
_subject_base: "{{ result_ipareplica_prepare._subject_base }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
@@ -335,10 +331,6 @@
|
||||
dirsrv_cert_files: "{{ ipareplica_dirsrv_cert_files | default([]) }}"
|
||||
### client ###
|
||||
force_join: "{{ ipaclient_force_join }}"
|
||||
### ad trust ###
|
||||
netbios_name: "{{ ipareplica_netbios_name | default(omit) }}"
|
||||
rid_base: "{{ ipareplica_rid_base | default(omit) }}"
|
||||
secondary_rid_base: "{{ ipareplica_secondary_rid_base | default(omit) }}"
|
||||
### additional ###
|
||||
server: "{{ result_ipareplica_test.server }}"
|
||||
config_master_host_name:
|
||||
@@ -352,7 +344,7 @@
|
||||
_add_to_ipaservers: "{{ result_ipareplica_prepare._add_to_ipaservers }}"
|
||||
_ca_subject: "{{ result_ipareplica_prepare._ca_subject }}"
|
||||
_subject_base: "{{ result_ipareplica_prepare._subject_base }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
|
||||
|
||||
- name: Install - Setup KRB
|
||||
@@ -367,9 +359,9 @@
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
_pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
|
||||
_pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
|
||||
# We need to point to the master in ipa default conf when certmonger
|
||||
# asks for HTTP certificate in newer ipa versions. In these versions
|
||||
@@ -393,10 +385,6 @@
|
||||
dirsrv_cert_files: "{{ ipareplica_dirsrv_cert_files | default([]) }}"
|
||||
### client ###
|
||||
force_join: "{{ ipaclient_force_join }}"
|
||||
### ad trust ###
|
||||
netbios_name: "{{ ipareplica_netbios_name | default(omit) }}"
|
||||
rid_base: "{{ ipareplica_rid_base | default(omit) }}"
|
||||
secondary_rid_base: "{{ ipareplica_secondary_rid_base | default(omit) }}"
|
||||
### additional ###
|
||||
server: "{{ result_ipareplica_test.server }}"
|
||||
config_master_host_name:
|
||||
@@ -410,7 +398,7 @@
|
||||
_add_to_ipaservers: "{{ result_ipareplica_prepare._add_to_ipaservers }}"
|
||||
_ca_subject: "{{ result_ipareplica_prepare._ca_subject }}"
|
||||
_subject_base: "{{ result_ipareplica_prepare._subject_base }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
|
||||
master:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
@@ -434,7 +422,7 @@
|
||||
_dirsrv_pkcs12_info: "{{ result_ipareplica_prepare._dirsrv_pkcs12_info if result_ipareplica_prepare._dirsrv_pkcs12_info != None else omit }}"
|
||||
_pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
ds_ca_subject: "{{ result_ipareplica_setup_ds.ds_ca_subject }}"
|
||||
|
||||
- name: Install - Setup http
|
||||
@@ -455,7 +443,7 @@
|
||||
_ca_file: "{{ result_ipareplica_prepare._ca_file }}"
|
||||
_http_pkcs12_info: "{{ result_ipareplica_prepare._http_pkcs12_info if result_ipareplica_prepare._http_pkcs12_info != None else omit }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
|
||||
# Need to point back to ourself after the cert for HTTP is obtained
|
||||
- name: Install - Create original IPA conf again
|
||||
@@ -477,10 +465,6 @@
|
||||
dirsrv_cert_files: "{{ ipareplica_dirsrv_cert_files | default([]) }}"
|
||||
### client ###
|
||||
force_join: "{{ ipaclient_force_join }}"
|
||||
### ad trust ###
|
||||
netbios_name: "{{ ipareplica_netbios_name | default(omit) }}"
|
||||
rid_base: "{{ ipareplica_rid_base | default(omit) }}"
|
||||
secondary_rid_base: "{{ ipareplica_secondary_rid_base | default(omit) }}"
|
||||
### additional ###
|
||||
server: "{{ result_ipareplica_test.server }}"
|
||||
config_master_host_name:
|
||||
@@ -494,7 +478,7 @@
|
||||
_add_to_ipaservers: "{{ result_ipareplica_prepare._add_to_ipaservers }}"
|
||||
_ca_subject: "{{ result_ipareplica_prepare._ca_subject }}"
|
||||
_subject_base: "{{ result_ipareplica_prepare._subject_base }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
|
||||
when: result_ipareplica_test.change_master_for_certmonger
|
||||
|
||||
@@ -513,7 +497,7 @@
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
_ca_file: "{{ result_ipareplica_prepare._ca_file }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
|
||||
- name: Install - Setup custodia
|
||||
ipareplica_setup_custodia:
|
||||
@@ -534,7 +518,7 @@
|
||||
_ca_file: "{{ result_ipareplica_prepare._ca_file }}"
|
||||
_pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
|
||||
- name: Install - Setup CA
|
||||
ipareplica_setup_ca:
|
||||
@@ -557,7 +541,7 @@
|
||||
_pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
_random_serial_numbers: "{{ result_ipareplica_prepare._random_serial_numbers }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_install_ca_certs.config_master_host_name }}"
|
||||
@@ -582,7 +566,7 @@
|
||||
_ca_file: "{{ result_ipareplica_prepare._ca_file }}"
|
||||
_pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
|
||||
- name: Install - DS apply updates
|
||||
ipareplica_ds_apply_updates:
|
||||
@@ -602,7 +586,7 @@
|
||||
_ca_file: "{{ result_ipareplica_prepare._ca_file }}"
|
||||
_pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
ds_ca_subject: "{{ result_ipareplica_setup_ds.ds_ca_subject }}"
|
||||
|
||||
- name: Install - Setup kra
|
||||
@@ -642,7 +626,7 @@
|
||||
_add_to_ipaservers: "{{ result_ipareplica_prepare._add_to_ipaservers }}"
|
||||
_ca_subject: "{{ result_ipareplica_prepare._ca_subject }}"
|
||||
_subject_base: "{{ result_ipareplica_prepare._subject_base }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
when: result_ipareplica_test.setup_kra
|
||||
|
||||
- name: Install - Restart KDC
|
||||
@@ -660,7 +644,7 @@
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
_ca_file: "{{ result_ipareplica_prepare._ca_file }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
|
||||
- name: Install - Custodia import dm password
|
||||
ipareplica_custodia_import_dm_password:
|
||||
@@ -681,7 +665,7 @@
|
||||
_kra_enabled: "{{ result_ipareplica_prepare._kra_enabled }}"
|
||||
_kra_host_name: "{{ result_ipareplica_prepare.config_kra_host_name }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
dirman_password: "{{ __derived_dirman_password }}"
|
||||
config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
|
||||
|
||||
- name: Install - Promote SSSD
|
||||
@@ -775,22 +759,17 @@
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
register: result_ipareplica_enable_ipa
|
||||
|
||||
always:
|
||||
- name: Install - Cleanup root IPA cache
|
||||
file:
|
||||
ansible.builtin.file:
|
||||
path: "/root/.ipa_cache"
|
||||
state: absent
|
||||
when: result_ipareplica_enable_ipa.changed
|
||||
|
||||
always:
|
||||
- name: Cleanup temporary files
|
||||
file:
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
with_items:
|
||||
- "/etc/ipa/.tmp_pkcs12_dirsrv"
|
||||
- "/etc/ipa/.tmp_pkcs12_http"
|
||||
- "/etc/ipa/.tmp_pkcs12_pkinit"
|
||||
|
||||
when: not ansible_check_mode and
|
||||
not (result_ipareplica_test.client_already_configured is defined or
|
||||
result_ipareplica_test.server_already_configured is defined)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# tasks file for ipareplica
|
||||
|
||||
- name: Import variables specific to distribution
|
||||
include_vars: "{{ item }}"
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "vars/{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_version'] }}.yml"
|
||||
- "vars/{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_major_version'] }}.yml"
|
||||
@@ -17,9 +17,9 @@
|
||||
- "vars/default.yml"
|
||||
|
||||
- name: Install IPA replica
|
||||
include_tasks: install.yml
|
||||
ansible.builtin.include_tasks: install.yml
|
||||
when: state|default('present') == 'present'
|
||||
|
||||
- name: Uninstall IPA replica
|
||||
include_tasks: uninstall.yml
|
||||
ansible.builtin.include_tasks: uninstall.yml
|
||||
when: state|default('present') == 'absent'
|
||||
|
||||
@@ -1,37 +1,19 @@
|
||||
---
|
||||
# tasks to uninstall IPA replica
|
||||
|
||||
- name: Uninstall - Uninstall IPA replica
|
||||
command: >
|
||||
/usr/sbin/ipa-server-install
|
||||
--uninstall
|
||||
-U
|
||||
{{ "--ignore-topology-disconnect" if
|
||||
ipareplica_ignore_topology_disconnect | bool else "" }}
|
||||
{{ "--ignore-last-of-role" if ipareplica_ignore_last_of_role | bool
|
||||
else "" }}
|
||||
register: result_uninstall
|
||||
# 2 means that uninstall failed because IPA replica was not configured
|
||||
failed_when: result_uninstall.rc != 0 and "'Env' object
|
||||
has no attribute 'basedn'" not in result_uninstall.stderr
|
||||
# IPA server is not configured on this system" not in
|
||||
# result_uninstall.stdout_lines
|
||||
changed_when: result_uninstall.rc == 0
|
||||
# until: result_uninstall.rc == 0
|
||||
retries: 2
|
||||
delay: 1
|
||||
- name: Set parameters
|
||||
ansible.builtin.set_fact:
|
||||
_ignore_topology_disconnect: "{{ ipaserver_ignore_topology_disconnect | default(ipareplica_ignore_topology_disconnect) | default(omit) }}"
|
||||
_ignore_last_of_role: "{{ ipaserver_ignore_last_of_role | default(ipareplica_ignore_last_of_role) | default(omit) }}"
|
||||
_remove_from_domain: "{{ ipaserver_remove_from_domain | default(ipareplica_remove_from_domain) | default(omit) }}"
|
||||
_remove_on_server: "{{ ipaserver_remove_on_server | default(ipareplica_remove_on_server) | default(omit) }}"
|
||||
|
||||
#- name: Uninstall - Remove all replication agreements and data about replica
|
||||
# command: >
|
||||
# /usr/sbin/ipa-replica-manage
|
||||
# del
|
||||
# {{ ipareplica_hostname | default(ansible_facts['fqdn']) }}
|
||||
# --force
|
||||
# --password={{ ipadm_password }}
|
||||
# failed_when: False
|
||||
# delegate_to: "{{ groups.ipaserver[0] | default(fail) }}"
|
||||
|
||||
#- name: Remove IPA replica packages
|
||||
# package:
|
||||
# name: "{{ ipareplica_packages }}"
|
||||
# state: absent
|
||||
- name: Uninstall - Uninstall replica
|
||||
ansible.builtin.include_role:
|
||||
name: ipaserver
|
||||
vars:
|
||||
state: absent
|
||||
ipaserver_ignore_topology_disconnect: "{{ _ignore_topology_disconnect | default(false) }}"
|
||||
ipaserver_ignore_last_of_role: "{{ _ignore_last_of_role | default(false) }}"
|
||||
ipaserver_remove_from_domain: "{{ _remove_from_domain | default(false) }}"
|
||||
ipaserver_remove_on_server: "{{ _remove_on_server | default(NULL) }}"
|
||||
|
||||
@@ -25,7 +25,7 @@ Supported Distributions
|
||||
|
||||
* RHEL/CentOS 7.6+
|
||||
* Fedora 26+
|
||||
* Ubuntu
|
||||
* Ubuntu 16.04 and 18.04
|
||||
|
||||
|
||||
Requirements
|
||||
@@ -79,7 +79,7 @@ Example playbook to setup the IPA server using admin and dirman passwords from a
|
||||
state: present
|
||||
```
|
||||
|
||||
Example playbook to unconfigure the IPA client(s) using principal and password from inventory file:
|
||||
Example playbook to unconfigure the IPA server using principal and password from inventory file:
|
||||
|
||||
```yaml
|
||||
---
|
||||
@@ -168,6 +168,64 @@ Server installation step 2: Copy `<ipaserver hostname>-chain.crt` to the IPA ser
|
||||
|
||||
The files can also be copied automatically: Set `ipaserver_copy_csr_to_controller` to true in the server installation step 1 and set `ipaserver_external_cert_files_from_controller` to point to the `chain.crt` file in the server installation step 2.
|
||||
|
||||
Since version 4.10, FreeIPA supports creating certificates using random serial numbers. Random serial numbers is a global and permanent setting, that can only be activated while deploying the first server of the domain. Replicas will inherit this setting automatically. An example of an inventory file to deploy a server with random serial numbers enabled is:
|
||||
|
||||
```ini
|
||||
[ipaserver]
|
||||
ipaserver.example.com
|
||||
|
||||
[ipaserver:vars]
|
||||
ipaserver_domain=example.com
|
||||
ipaserver_realm=EXAMPLE.COM
|
||||
ipaadmin_password=MySecretPassword123
|
||||
ipadm_password=MySecretPassword234
|
||||
ipaserver_random_serial_numbers=true
|
||||
```
|
||||
|
||||
By setting the variable in the inventory file, the same ipaserver deployment playbook, shown before, can be used.
|
||||
|
||||
|
||||
Example inventory file to remove a server from the domain:
|
||||
|
||||
```ini
|
||||
[ipaserver]
|
||||
ipaserver.example.com
|
||||
|
||||
[ipaserver:vars]
|
||||
ipaadmin_password=MySecretPassword123
|
||||
ipaserver_remove_from_domain=true
|
||||
```
|
||||
|
||||
Example playbook to remove an IPA server using admin passwords from the domain:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to remove IPA server
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- role: ipaserver
|
||||
state: absent
|
||||
```
|
||||
|
||||
The inventory will enable the removal of the server (also a replica) from the domain. Additional options are needed if the removal of the server/replica is resulting in a topology disconnect or if the server/replica is the last that has a role.
|
||||
|
||||
To continue with the removal with a topology disconnect it is needed to set these parameters:
|
||||
|
||||
```ini
|
||||
ipaserver_ignore_topology_disconnect=true
|
||||
ipaserver_remove_on_server=ipaserver2.example.com
|
||||
```
|
||||
|
||||
To continue with the removal for a server that is the last that has a role:
|
||||
|
||||
```ini
|
||||
ipaserver_ignore_last_of_role=true
|
||||
```
|
||||
|
||||
Be careful with enabling the `ipaserver_ignore_topology_disconnect` and especially `ipaserver_ignore_last_of_role`, the change can not be reverted easily.
|
||||
|
||||
|
||||
Playbooks
|
||||
=========
|
||||
@@ -221,6 +279,7 @@ Variable | Description | Required
|
||||
`ipaserver_no_ui_redirect` | Do not automatically redirect to the Web UI. (bool) | no
|
||||
`ipaserver_dirsrv_config_file` | The path to LDIF file that will be used to modify configuration of dse.ldif during installation. (string) | no
|
||||
`ipaserver_pki_config_override` | Path to ini file with config overrides. This is only usable with recent FreeIPA versions. (string) | no
|
||||
`ipaserver_random_serial_numbers` | Enable use of random serial numbers for certificates. Requires FreeIPA version 4.10 or later. (boolean) | no
|
||||
|
||||
SSL certificate Variables
|
||||
-------------------------
|
||||
@@ -252,6 +311,7 @@ Variable | Description | Required
|
||||
`ipaclient_no_ssh` | The bool value defines if OpenSSH client will be configured. `ipaclient_no_ssh` defaults to `no`. | no
|
||||
`ipaclient_no_sshd` | The bool value defines if OpenSSH server will be configured. `ipaclient_no_sshd` defaults to `no`. | no
|
||||
`ipaclient_no_sudo` | The bool value defines if SSSD will be configured as a data source for sudo. `ipaclient_no_sudo` defaults to `no`. | no
|
||||
`ipaclient_subid` | The bool value defines if SSSD will be configured as a data source for subid. `ipaclient_subid` defaults to `no`. | no
|
||||
`ipaclient_no_dns_sshfp` | The bool value defines if DNS SSHFP records will not be created automatically. `ipaclient_no_dns_sshfp` defaults to `no`. | no
|
||||
|
||||
Certificate system Variables
|
||||
@@ -304,6 +364,19 @@ Variable | Description | Required
|
||||
`ipaserver_external_cert_files_from_controller` | Files containing the IPA CA certificates and the external CA certificate chains on the controller that will be copied to the ipaserver host to `/root` folder. (list of string) | no
|
||||
`ipaserver_copy_csr_to_controller` | Copy the generated CSR from the ipaserver to the controller as `"{{ inventory_hostname }}-ipa.csr"`. (bool) | no
|
||||
|
||||
Undeploy Variables (`state`: absent)
|
||||
------------------------------------
|
||||
|
||||
These settings should only be used if the result is really wanted. The change might not be revertable easily.
|
||||
|
||||
Variable | Description | Required
|
||||
-------- | ----------- | --------
|
||||
`ipaserver_ignore_topology_disconnect` | If enabled this enforces the removal of the server even if it results in a topology disconnect. Be careful with this setting. (bool) | false
|
||||
`ipaserver_ignore_last_of_role` | If enabled this enforces the removal of the server even if the server is the last with one that has a role. Be careful, this might not be revered easily. (bool) | false
|
||||
`ipaserver_remove_from_domain` | This enables the removal of the server from the domain additionally to the undeployment. (bool) | false
|
||||
`ipaserver_remove_on_server` | The value defines the server/replica in the domain that will to be used to remove the server/replica from the domain if `ipaserver_ignore_topology_disconnect` and `ipaserver_remove_from_domain` are enabled. Without the need to enable `ipaserver_ignore_topology_disconnect`, the value will be automatically detected using the replication agreements of the server/replica. (string) | false
|
||||
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ ipaserver_no_hbac_allow: no
|
||||
ipaserver_no_pkinit: no
|
||||
ipaserver_no_ui_redirect: no
|
||||
ipaserver_mem_check: yes
|
||||
ipaserver_random_serial_numbers: false
|
||||
### ssl certificate ###
|
||||
### client ###
|
||||
ipaclient_mkhomedir: no
|
||||
@@ -42,3 +43,4 @@ ipaserver_copy_csr_to_controller: no
|
||||
### uninstall ###
|
||||
ipaserver_ignore_topology_disconnect: no
|
||||
ipaserver_ignore_last_of_role: no
|
||||
ipaserver_remove_from_domain: false
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user