mirror of
https://github.com/freeipa/ansible-freeipa.git
synced 2026-03-28 06:13:05 +00:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -35,6 +35,7 @@ skip_list:
|
||||
- yaml # yamllint should be executed separately.
|
||||
- experimental # Do not run any experimental tests
|
||||
- name[template] # Allow Jinja templating inside task names
|
||||
- var-naming
|
||||
|
||||
use_default_rules: true
|
||||
|
||||
|
||||
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/
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -393,8 +393,8 @@ 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
|
||||
`city` | City | no
|
||||
`userstate` \| `st` | State/Province | no
|
||||
`postalcode` \| `zip` | Postalcode/ZIP | no
|
||||
@@ -434,8 +434,8 @@ 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
|
||||
|
||||
@@ -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"
|
||||
|
||||
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
|
||||
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
|
||||
@@ -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"]
|
||||
|
||||
import os
|
||||
# ansible-freeipa requires locale to be C, IPA requires utf-8.
|
||||
@@ -106,6 +107,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 +749,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():
|
||||
|
||||
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()
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
@@ -646,10 +669,10 @@ def check_parameters( # pylint: disable=unused-argument
|
||||
|
||||
# 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 +694,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:
|
||||
@@ -1468,7 +1491,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:
|
||||
|
||||
@@ -197,7 +197,7 @@ def gen_args(module,
|
||||
if maxrepeat is not None:
|
||||
_args["ipapwdmaxrepeat"] = maxrepeat
|
||||
if maxsequence is not None:
|
||||
_args["ipapwdmaxrsequence"] = maxsequence
|
||||
_args["ipapwdmaxsequence"] = maxsequence
|
||||
if dictcheck is not None:
|
||||
if module.ipa_check_version("<", "4.9.10"):
|
||||
# Allowed values: "TRUE", "FALSE", ""
|
||||
@@ -230,17 +230,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'.")
|
||||
|
||||
@@ -275,7 +273,7 @@ def main():
|
||||
default=None),
|
||||
dictcheck=dict(type="str", aliases=["ipapwdictcheck"],
|
||||
default=None),
|
||||
usercheck=dict(type="str", aliases=["ipapwusercheck"],
|
||||
usercheck=dict(type="str", aliases=["ipapwdusercheck"],
|
||||
default=None),
|
||||
gracelimit=dict(type="str", aliases=["passwordgracelimit"],
|
||||
default=None),
|
||||
|
||||
@@ -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", ""]
|
||||
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
|
||||
@@ -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:
|
||||
@@ -360,67 +492,85 @@ def check_parameters(module, state, action, names):
|
||||
|
||||
|
||||
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", ""]),
|
||||
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 +586,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 +619,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
|
||||
@@ -480,8 +645,45 @@ def main():
|
||||
|
||||
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")
|
||||
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 = []
|
||||
|
||||
|
||||
@@ -124,12 +124,12 @@ 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"]
|
||||
@@ -348,12 +348,12 @@ 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"]
|
||||
@@ -548,6 +548,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,6 +584,14 @@ 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
|
||||
@@ -589,10 +608,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:
|
||||
@@ -834,11 +855,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 +882,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:
|
||||
@@ -1511,7 +1532,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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -239,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) }}"
|
||||
@@ -255,6 +262,7 @@
|
||||
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
|
||||
@@ -323,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
|
||||
@@ -351,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
|
||||
@@ -378,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) }}"
|
||||
@@ -386,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:
|
||||
@@ -412,6 +410,36 @@
|
||||
domain: "{{ result_ipaclient_test.domain }}"
|
||||
nisdomain: "{{ ipaclient_nisdomain | default(omit) }}"
|
||||
when: not ipaclient_no_nisdomain | bool
|
||||
|
||||
- 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: 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
|
||||
@@ -423,3 +451,15 @@
|
||||
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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -255,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 }}"
|
||||
@@ -297,10 +293,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 }}"
|
||||
@@ -339,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:
|
||||
@@ -397,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:
|
||||
@@ -481,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:
|
||||
@@ -779,13 +759,12 @@
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
register: result_ipareplica_enable_ipa
|
||||
|
||||
always:
|
||||
- name: Install - Cleanup root IPA cache
|
||||
ansible.builtin.file:
|
||||
path: "/root/.ipa_cache"
|
||||
state: absent
|
||||
when: result_ipareplica_enable_ipa.changed
|
||||
|
||||
always:
|
||||
- name: Cleanup temporary files
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
|
||||
@@ -1,37 +1,19 @@
|
||||
---
|
||||
# tasks to uninstall IPA replica
|
||||
|
||||
- name: Uninstall - Uninstall IPA replica
|
||||
ansible.builtin.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
|
||||
# ansible.builtin.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
|
||||
# ansible.builtin.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) }}"
|
||||
|
||||
@@ -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_number=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
|
||||
|
||||
264
roles/ipaserver/library/ipaserver_get_connected_server.py
Normal file
264
roles/ipaserver/library/ipaserver_get_connected_server.py
Normal file
@@ -0,0 +1,264 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2019-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: ipaserver_get_connected_server
|
||||
short_description: Get connected servers for server
|
||||
description: Get connected servers for server
|
||||
options:
|
||||
ipaadmin_principal:
|
||||
description: The admin principal.
|
||||
default: admin
|
||||
type: str
|
||||
ipaadmin_password:
|
||||
description: The admin password.
|
||||
required: true
|
||||
type: str
|
||||
hostname:
|
||||
description: The FQDN server name.
|
||||
type: str
|
||||
required: true
|
||||
author:
|
||||
- Thomas Woerner (@t-woerner)
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
server:
|
||||
description: Connected server name
|
||||
returned: always
|
||||
type: str
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import shutil
|
||||
from contextlib import contextmanager
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils import six
|
||||
|
||||
try:
|
||||
from ipalib import api
|
||||
from ipalib import errors as ipalib_errors # noqa
|
||||
from ipalib.config import Env
|
||||
from ipaplatform.paths import paths
|
||||
from ipapython.ipautil import run
|
||||
from ipalib.constants import DEFAULT_CONFIG
|
||||
try:
|
||||
from ipalib.install.kinit import kinit_password
|
||||
except ImportError:
|
||||
from ipapython.ipautil import kinit_password
|
||||
except ImportError as _err:
|
||||
MODULE_IMPORT_ERROR = str(_err)
|
||||
else:
|
||||
MODULE_IMPORT_ERROR = None
|
||||
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
|
||||
def temp_kinit(principal, password):
|
||||
"""Kinit with password using a temporary ccache."""
|
||||
ccache_dir = tempfile.mkdtemp(prefix='krbcc')
|
||||
ccache_name = os.path.join(ccache_dir, 'ccache')
|
||||
|
||||
try:
|
||||
kinit_password(principal, password, ccache_name)
|
||||
except RuntimeError as e:
|
||||
raise RuntimeError("Kerberos authentication failed: %s" % str(e))
|
||||
|
||||
os.environ["KRB5CCNAME"] = ccache_name
|
||||
return ccache_dir, ccache_name
|
||||
|
||||
|
||||
def temp_kdestroy(ccache_dir, ccache_name):
|
||||
"""Destroy temporary ticket and remove temporary ccache."""
|
||||
if ccache_name is not None:
|
||||
run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False)
|
||||
del os.environ['KRB5CCNAME']
|
||||
if ccache_dir is not None:
|
||||
shutil.rmtree(ccache_dir, ignore_errors=True)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def ipa_connect(module, principal=None, password=None):
|
||||
"""
|
||||
Create a context with a connection to IPA API.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module: AnsibleModule
|
||||
The AnsibleModule to use
|
||||
principal: string
|
||||
The optional principal name
|
||||
password: string
|
||||
The admin password.
|
||||
|
||||
"""
|
||||
if not password:
|
||||
module.fail_json(msg="Password is required.")
|
||||
if not principal:
|
||||
principal = "admin"
|
||||
|
||||
ccache_dir = None
|
||||
ccache_name = None
|
||||
try:
|
||||
ccache_dir, ccache_name = temp_kinit(principal, password)
|
||||
# api_connect start
|
||||
env = Env()
|
||||
env._bootstrap()
|
||||
env._finalize_core(**dict(DEFAULT_CONFIG))
|
||||
|
||||
api.bootstrap(context="server", debug=env.debug, log=None)
|
||||
api.finalize()
|
||||
|
||||
if api.env.in_server:
|
||||
backend = api.Backend.ldap2
|
||||
else:
|
||||
backend = api.Backend.rpcclient
|
||||
|
||||
if not backend.isconnected():
|
||||
backend.connect(ccache=ccache_name)
|
||||
# api_connect end
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e))
|
||||
else:
|
||||
try:
|
||||
yield ccache_name
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e))
|
||||
finally:
|
||||
temp_kdestroy(ccache_dir, ccache_name)
|
||||
|
||||
|
||||
def ipa_command(command, name, args):
|
||||
"""
|
||||
Execute an IPA API command with a required `name` argument.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
command: string
|
||||
The IPA API command to execute.
|
||||
name: string
|
||||
The name parameter to pass to the command.
|
||||
args: dict
|
||||
The parameters to pass to the command.
|
||||
|
||||
"""
|
||||
return api.Command[command](name, **args)
|
||||
|
||||
|
||||
def _afm_convert(value):
|
||||
if value is not None:
|
||||
if isinstance(value, list):
|
||||
return [_afm_convert(x) for x in value]
|
||||
if isinstance(value, dict):
|
||||
return {_afm_convert(k): _afm_convert(v)
|
||||
for k, v in value.items()}
|
||||
if isinstance(value, str):
|
||||
return to_text(value)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def module_params_get(module, name):
|
||||
return _afm_convert(module.params.get(name))
|
||||
|
||||
|
||||
def host_show(module, name):
|
||||
_args = {
|
||||
"all": True,
|
||||
}
|
||||
|
||||
try:
|
||||
_result = ipa_command("host_show", name, _args)
|
||||
except ipalib_errors.NotFound as e:
|
||||
msg = str(e)
|
||||
if "host not found" in msg:
|
||||
return None
|
||||
module.fail_json(msg="host_show failed: %s" % msg)
|
||||
|
||||
return _result["result"]
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
ipaadmin_principal=dict(type="str", default="admin"),
|
||||
ipaadmin_password=dict(type="str", required=True, no_log=True),
|
||||
hostname=dict(type="str", required=True),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if MODULE_IMPORT_ERROR is not None:
|
||||
module.fail_json(msg=MODULE_IMPORT_ERROR)
|
||||
|
||||
# In check mode always return changed.
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
ipaadmin_principal = module_params_get(module, "ipaadmin_principal")
|
||||
ipaadmin_password = module_params_get(module, "ipaadmin_password")
|
||||
hostname = module_params_get(module, "hostname")
|
||||
|
||||
server = None
|
||||
right_left = ["iparepltoposegmentrightnode", "iparepltoposegmentleftnode"]
|
||||
with ipa_connect(module, ipaadmin_principal, ipaadmin_password):
|
||||
# At first search in the domain, then ca suffix:
|
||||
# Search for the first iparepltoposegmentleftnode (node 2), where
|
||||
# iparepltoposegmentrightnode is hostname (node 1), then for the
|
||||
# first iparepltoposegmentrightnode (node 2) where
|
||||
# iparepltoposegmentleftnode is hostname (node 1).
|
||||
for suffix_name in ["domain", "ca"]:
|
||||
for node1, node2 in [[right_left[0], right_left[1]],
|
||||
[right_left[1], right_left[0]]]:
|
||||
args = {node1: hostname}
|
||||
result = api.Command.topologysegment_find(
|
||||
suffix_name, **args)
|
||||
if result and "result" in result and len(result["result"]) > 0:
|
||||
res = result["result"][0]
|
||||
if node2 in res:
|
||||
if len(res[node2]) > 0:
|
||||
server = res[node2][0]
|
||||
break
|
||||
if server is not None:
|
||||
module.exit_json(changed=False, server=server)
|
||||
module.exit_json(changed=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -208,6 +208,11 @@ options:
|
||||
description: The installer ca_subject setting
|
||||
type: str
|
||||
required: no
|
||||
random_serial_numbers:
|
||||
description: The installer random_serial_numbers setting
|
||||
type: bool
|
||||
default: no
|
||||
required: no
|
||||
allow_zone_overlap:
|
||||
description: Create DNS zone even if it already exists
|
||||
type: bool
|
||||
@@ -304,7 +309,7 @@ from ansible.module_utils.ansible_ipa_server import (
|
||||
check_dirsrv, ScriptError, get_fqdn, verify_fqdn, BadHostError,
|
||||
validate_domain_name, load_pkcs12, IPA_PYTHON_VERSION,
|
||||
encode_certificate, check_available_memory, getargspec, adtrustinstance,
|
||||
get_min_idstart
|
||||
get_min_idstart, SerialNumber
|
||||
)
|
||||
from ansible.module_utils import six
|
||||
|
||||
@@ -369,6 +374,8 @@ def main():
|
||||
elements='str', default=None),
|
||||
subject_base=dict(required=False, type='str'),
|
||||
ca_subject=dict(required=False, type='str'),
|
||||
random_serial_numbers=dict(required=False, type='bool',
|
||||
default=False),
|
||||
# ca_signing_algorithm
|
||||
# dns
|
||||
allow_zone_overlap=dict(required=False, type='bool',
|
||||
@@ -456,6 +463,8 @@ def main():
|
||||
'external_cert_files')
|
||||
options.subject_base = ansible_module.params.get('subject_base')
|
||||
options.ca_subject = ansible_module.params.get('ca_subject')
|
||||
options._random_serial_numbers = ansible_module.params.get(
|
||||
'random_serial_numbers')
|
||||
# ca_signing_algorithm
|
||||
# dns
|
||||
options.allow_zone_overlap = ansible_module.params.get(
|
||||
@@ -513,6 +522,12 @@ def main():
|
||||
ansible_module.fail_json(
|
||||
msg="pki_config_override: %s" % str(e))
|
||||
|
||||
# Check if Random Serial Numbers v3 is available
|
||||
if options._random_serial_numbers and SerialNumber is None:
|
||||
ansible_module.fail_json(
|
||||
msg="Random Serial Numbers is not supported for this IPA version"
|
||||
)
|
||||
|
||||
# default values ########################################################
|
||||
|
||||
# idstart and idmax
|
||||
@@ -1040,6 +1055,11 @@ def main():
|
||||
|
||||
domain_name = domain_name.lower()
|
||||
|
||||
# Both host_name and domain_name are lowercase at this point.
|
||||
if host_name == domain_name:
|
||||
ansible_module.fail_json(
|
||||
msg="hostname cannot be the same as the domain name")
|
||||
|
||||
if not options.realm_name:
|
||||
realm_name = domain_name.upper()
|
||||
else:
|
||||
@@ -1053,7 +1073,7 @@ def main():
|
||||
try:
|
||||
validate_domain_name(realm_name, entity="realm")
|
||||
except ValueError as e:
|
||||
raise ScriptError("Invalid realm name: {}".format(unicode(e)))
|
||||
raise ScriptError("Invalid realm name: {0}".format(unicode(e)))
|
||||
|
||||
if not options.setup_adtrust:
|
||||
# If domain name and realm does not match, IPA server will not be able
|
||||
@@ -1147,42 +1167,45 @@ def main():
|
||||
pkinit_pkcs12_info = ("/etc/ipa/.tmp_pkcs12_pkinit", pkinit_pin)
|
||||
pkinit_ca_cert = encode_certificate(pkinit_ca_cert)
|
||||
|
||||
ansible_module.exit_json(changed=False,
|
||||
ipa_python_version=IPA_PYTHON_VERSION,
|
||||
# basic
|
||||
domain=options.domain_name,
|
||||
realm=realm_name,
|
||||
hostname=host_name,
|
||||
_hostname_overridden=bool(options.host_name),
|
||||
no_host_dns=options.no_host_dns,
|
||||
# server
|
||||
setup_adtrust=options.setup_adtrust,
|
||||
setup_kra=options.setup_kra,
|
||||
setup_ca=options.setup_ca,
|
||||
idstart=options.idstart,
|
||||
idmax=options.idmax,
|
||||
no_pkinit=options.no_pkinit,
|
||||
# ssl certificate
|
||||
_dirsrv_pkcs12_info=dirsrv_pkcs12_info,
|
||||
_dirsrv_ca_cert=dirsrv_ca_cert,
|
||||
_http_pkcs12_info=http_pkcs12_info,
|
||||
_http_ca_cert=http_ca_cert,
|
||||
_pkinit_pkcs12_info=pkinit_pkcs12_info,
|
||||
_pkinit_ca_cert=pkinit_ca_cert,
|
||||
# certificate system
|
||||
external_ca=options.external_ca,
|
||||
external_ca_type=options.external_ca_type,
|
||||
external_ca_profile=options.external_ca_profile,
|
||||
# ad trust
|
||||
rid_base=options.rid_base,
|
||||
secondary_rid_base=options.secondary_rid_base,
|
||||
# client
|
||||
ntp_servers=options.ntp_servers,
|
||||
ntp_pool=options.ntp_pool,
|
||||
# additional
|
||||
_installation_cleanup=_installation_cleanup,
|
||||
domainlevel=options.domainlevel,
|
||||
sid_generation_always=sid_generation_always)
|
||||
ansible_module.exit_json(
|
||||
changed=False,
|
||||
ipa_python_version=IPA_PYTHON_VERSION,
|
||||
# basic
|
||||
domain=options.domain_name,
|
||||
realm=realm_name,
|
||||
hostname=host_name,
|
||||
_hostname_overridden=bool(options.host_name),
|
||||
no_host_dns=options.no_host_dns,
|
||||
# server
|
||||
setup_adtrust=options.setup_adtrust,
|
||||
setup_kra=options.setup_kra,
|
||||
setup_ca=options.setup_ca,
|
||||
idstart=options.idstart,
|
||||
idmax=options.idmax,
|
||||
no_pkinit=options.no_pkinit,
|
||||
# ssl certificate
|
||||
_dirsrv_pkcs12_info=dirsrv_pkcs12_info,
|
||||
_dirsrv_ca_cert=dirsrv_ca_cert,
|
||||
_http_pkcs12_info=http_pkcs12_info,
|
||||
_http_ca_cert=http_ca_cert,
|
||||
_pkinit_pkcs12_info=pkinit_pkcs12_info,
|
||||
_pkinit_ca_cert=pkinit_ca_cert,
|
||||
# certificate system
|
||||
external_ca=options.external_ca,
|
||||
external_ca_type=options.external_ca_type,
|
||||
external_ca_profile=options.external_ca_profile,
|
||||
# ad trust
|
||||
rid_base=options.rid_base,
|
||||
secondary_rid_base=options.secondary_rid_base,
|
||||
# client
|
||||
ntp_servers=options.ntp_servers,
|
||||
ntp_pool=options.ntp_pool,
|
||||
# additional
|
||||
_installation_cleanup=_installation_cleanup,
|
||||
domainlevel=options.domainlevel,
|
||||
sid_generation_always=sid_generation_always,
|
||||
random_serial_numbers=options._random_serial_numbers,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -44,7 +44,7 @@ __all__ = ["IPAChangeConf", "certmonger", "sysrestore", "root_logger",
|
||||
"check_available_memory", "getargspec", "get_min_idstart",
|
||||
"paths", "api", "ipautil", "adtrust_imported", "NUM_VERSION",
|
||||
"time_service", "kra_imported", "dsinstance", "IPA_PYTHON_VERSION",
|
||||
"NUM_VERSION"]
|
||||
"NUM_VERSION", "SerialNumber"]
|
||||
|
||||
import sys
|
||||
import logging
|
||||
@@ -203,6 +203,13 @@ try:
|
||||
except ImportError:
|
||||
get_min_idstart = None
|
||||
|
||||
# SerialNumber is defined in versions 4.10 and later and is
|
||||
# used by Random Serian Number v3.
|
||||
try:
|
||||
from ipalib.parameters import SerialNumber
|
||||
except ImportError:
|
||||
SerialNumber = None
|
||||
|
||||
else:
|
||||
# IPA version < 4.5
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
external_cert_files: "{{ ipaserver_external_cert_files | default(omit) }}"
|
||||
subject_base: "{{ ipaserver_subject_base | default(omit) }}"
|
||||
ca_subject: "{{ ipaserver_ca_subject | default(omit) }}"
|
||||
random_serial_numbers: "{{ ipaserver_random_serial_numbers | default(omit) }}"
|
||||
# ca_signing_algorithm
|
||||
### dns ###
|
||||
allow_zone_overlap: "{{ ipaserver_allow_zone_overlap }}"
|
||||
@@ -199,7 +200,7 @@
|
||||
### additional ###
|
||||
setup_ca: "{{ result_ipaserver_test.setup_ca }}"
|
||||
sid_generation_always: "{{ result_ipaserver_test.sid_generation_always }}"
|
||||
random_serial_numbers: no
|
||||
random_serial_numbers: "{{ result_ipaserver_test.random_serial_numbers }}"
|
||||
_hostname_overridden: "{{ result_ipaserver_test._hostname_overridden }}"
|
||||
register: result_ipaserver_prepare
|
||||
|
||||
@@ -446,12 +447,6 @@
|
||||
setup_ca: "{{ result_ipaserver_test.setup_ca }}"
|
||||
register: result_ipaserver_enable_ipa
|
||||
|
||||
- name: Install - Cleanup root IPA cache
|
||||
ansible.builtin.file:
|
||||
path: "/root/.ipa_cache"
|
||||
state: absent
|
||||
when: result_ipaserver_enable_ipa.changed
|
||||
|
||||
- name: Install - Configure firewalld
|
||||
ansible.builtin.command: >
|
||||
firewall-cmd
|
||||
@@ -480,6 +475,11 @@
|
||||
when: ipaserver_setup_firewalld | bool
|
||||
|
||||
always:
|
||||
- name: Install - Cleanup root IPA cache
|
||||
ansible.builtin.file:
|
||||
path: "/root/.ipa_cache"
|
||||
state: absent
|
||||
|
||||
- name: Cleanup temporary files
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
|
||||
@@ -1,6 +1,47 @@
|
||||
---
|
||||
# tasks to uninstall IPA server
|
||||
|
||||
- name: Uninstall - Set server hostname for removal
|
||||
ansible.builtin.set_fact:
|
||||
_remove_hostname: "{{ ansible_facts['fqdn'] }}"
|
||||
|
||||
- name: Uninstall - Remove server
|
||||
when: ipaserver_remove_from_domain
|
||||
block:
|
||||
|
||||
- name: Uninstall - Fail on missing ipaadmin_password for server removal
|
||||
ansible.builtin.fail:
|
||||
msg: "'ipaadmin_password' is needed for 'ipaserver_remove_from_domain'"
|
||||
when: ipaadmin_password is not defined
|
||||
|
||||
- name: Uninstall - Fail on missing ipaserver_remove_on_server with ipaserver_ignore_topology_disconnect
|
||||
ansible.builtin.fail:
|
||||
msg: "'ipaserver_remove_on_server' is needed for 'ipaserver_remove_from_domain' with 'ipaserver_ignore_topology_disconnect'"
|
||||
when: ipaserver_ignore_topology_disconnect | bool
|
||||
and ipaserver_remove_on_server is not defined
|
||||
|
||||
- name: Uninstall - Get connected server
|
||||
ipaserver_get_connected_server:
|
||||
ipaadmin_principal: "{{ ipaadmin_principal | default('admin') }}"
|
||||
ipaadmin_password: "{{ ipaadmin_password }}"
|
||||
hostname: "{{ _remove_hostname }}"
|
||||
register: result_get_connected_server
|
||||
when: ipaserver_remove_on_server is not defined
|
||||
|
||||
# REMOVE SERVER FROM DOMAIN
|
||||
- name: Uninstall - Server del "{{ _remove_hostname }}"
|
||||
ipaserver:
|
||||
ipaadmin_principal: "{{ ipaadmin_principal | default('admin') }}"
|
||||
ipaadmin_password: "{{ ipaadmin_password }}"
|
||||
name: "{{ _remove_hostname }}"
|
||||
ignore_last_of_role: "{{ ipaserver_ignore_last_of_role }}"
|
||||
ignore_topology_disconnect: "{{ ipaserver_ignore_topology_disconnect }}"
|
||||
# delete_continue: "{{ ipaserver_delete_continue }}"
|
||||
state: absent
|
||||
delegate_to: "{{ ipaserver_remove_on_server | default(result_get_connected_server.server) }}"
|
||||
when: ipaserver_remove_on_server is defined or
|
||||
result_get_connected_server.server is defined
|
||||
|
||||
- name: Uninstall - Uninstall IPA server
|
||||
ansible.builtin.command: >
|
||||
/usr/sbin/ipa-server-install
|
||||
|
||||
@@ -60,6 +60,7 @@ disable =
|
||||
[pylint.BASIC]
|
||||
good-names =
|
||||
ex, i, j, k, Run, _, e, x, dn, cn, ip, os, unicode, __metaclass__, ds,
|
||||
dt, ca,
|
||||
# These are utils tools, and not part of the released collection.
|
||||
galaxyfy-playbook, galaxyfy-README, galaxyfy-module-EXAMPLES,
|
||||
module_EXAMPLES
|
||||
|
||||
@@ -105,7 +105,7 @@ It's also possible to run the tests in a container.
|
||||
Before setting up a container you will need to install molecule framework:
|
||||
|
||||
```
|
||||
pip install molecule[docker]>=3
|
||||
pip install molecule-plugins[docker]
|
||||
```
|
||||
|
||||
Now you can start a test container using the following command:
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
retryCountOnTaskFailure: 5
|
||||
displayName: Install tools
|
||||
|
||||
- script: pip install molecule[docker]
|
||||
- script: pip install molecule-plugins[docker] "requests<2.29"
|
||||
retryCountOnTaskFailure: 5
|
||||
displayName: Install molecule
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@ jobs:
|
||||
|
||||
- script: |
|
||||
pip install \
|
||||
"molecule[docker]>=3" \
|
||||
"molecule-plugins[docker]" \
|
||||
"requests<2.29" \
|
||||
"ansible${{ parameters.ansible_version }}"
|
||||
retryCountOnTaskFailure: 5
|
||||
displayName: Install molecule and Ansible
|
||||
|
||||
@@ -33,7 +33,8 @@ jobs:
|
||||
|
||||
- script: |
|
||||
pip install \
|
||||
"molecule[docker]>=3" \
|
||||
"molecule-plugins[docker]" \
|
||||
"requests<2.29" \
|
||||
"ansible${{ parameters.ansible_version }}"
|
||||
retryCountOnTaskFailure: 5
|
||||
displayName: Install molecule and Ansible
|
||||
|
||||
@@ -34,8 +34,9 @@ jobs:
|
||||
scenario: ${{ parameters.scenario }}
|
||||
ansible_version: ${{ parameters.ansible_version }}
|
||||
|
||||
- template: galaxy_pytest_script.yml
|
||||
parameters:
|
||||
build_number: ${{ parameters.build_number }}
|
||||
scenario: ${{ parameters.scenario }}
|
||||
ansible_version: ${{ parameters.ansible_version }}
|
||||
# Temporarily disable due to issues with ansible docker plugin.
|
||||
#- template: galaxy_pytest_script.yml
|
||||
# parameters:
|
||||
# build_number: ${{ parameters.build_number }}
|
||||
# scenario: ${{ parameters.scenario }}
|
||||
# ansible_version: ${{ parameters.ansible_version }}
|
||||
|
||||
@@ -34,8 +34,9 @@ jobs:
|
||||
scenario: ${{ parameters.scenario }}
|
||||
ansible_version: ${{ parameters.ansible_version }}
|
||||
|
||||
- template: pytest_tests.yml
|
||||
parameters:
|
||||
build_number: ${{ parameters.build_number }}
|
||||
scenario: ${{ parameters.scenario }}
|
||||
ansible_version: ${{ parameters.ansible_version }}
|
||||
# Temporarily disabled due to ansible docker plugin issue.
|
||||
#- template: pytest_tests.yml
|
||||
# parameters:
|
||||
# build_number: ${{ parameters.build_number }}
|
||||
# scenario: ${{ parameters.scenario }}
|
||||
# ansible_version: ${{ parameters.ansible_version }}
|
||||
|
||||
@@ -32,7 +32,8 @@ jobs:
|
||||
|
||||
- script: |
|
||||
pip install \
|
||||
"molecule[docker]>=3" \
|
||||
"molecule-plugins[docker]" \
|
||||
"requests<2.29" \
|
||||
"ansible${{ parameters.ansible_version }}"
|
||||
retryCountOnTaskFailure: 5
|
||||
displayName: Install molecule and Ansible
|
||||
|
||||
@@ -32,7 +32,8 @@ jobs:
|
||||
|
||||
- script: |
|
||||
pip install \
|
||||
"molecule[docker]>=3" \
|
||||
"molecule-plugins[docker]" \
|
||||
"requests<2.29" \
|
||||
"ansible${{ parameters.ansible_version }}"
|
||||
retryCountOnTaskFailure: 5
|
||||
displayName: Install molecule and Ansible
|
||||
|
||||
@@ -26,7 +26,8 @@ jobs:
|
||||
|
||||
- script: |
|
||||
pip install \
|
||||
"molecule[docker]>=3" \
|
||||
"molecule-plugins[docker]" \
|
||||
"requests<2.29" \
|
||||
"ansible${{ parameters.ansible_version }}"
|
||||
retryCountOnTaskFailure: 5
|
||||
displayName: Install molecule and Ansible
|
||||
|
||||
60
tests/cert/test_cert_client_context.yml
Normal file
60
tests/cert/test_cert_client_context.yml
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
- name: Test cert
|
||||
hosts: ipaclients, ipaserver
|
||||
become: false
|
||||
gather_facts: false
|
||||
module_defaults:
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_contetx: "{{ ipa_context | default(omit) }}"
|
||||
|
||||
tasks:
|
||||
- name: Include FreeIPA facts.
|
||||
ansible.builtin.include_tasks: ../env_freeipa_facts.yml
|
||||
|
||||
# Test will only be executed if host is not a server.
|
||||
- name: Execute with server context in the client.
|
||||
ipacert:
|
||||
ipaapi_context: server
|
||||
name: ThisShouldNotWork
|
||||
register: result
|
||||
failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
|
||||
when: ipa_host_is_client
|
||||
|
||||
# Import basic module tests, and execute with ipa_context set to 'client'.
|
||||
# If ipaclients is set, it will be executed using the client, if not,
|
||||
# ipaserver will be used.
|
||||
#
|
||||
# With this setup, tests can be executed against an IPA client, against
|
||||
# an IPA server using "client" context, and ensure that tests are executed
|
||||
# in upstream CI.
|
||||
|
||||
- name: Test host certs using client context, in client host.
|
||||
ansible.builtin.import_playbook: test_cert_host.yml
|
||||
when: groups['ipaclients']
|
||||
vars:
|
||||
ipa_test_host: ipaclients
|
||||
|
||||
- name: Test service certs using client context, in client host.
|
||||
ansible.builtin.import_playbook: test_cert_service.yml
|
||||
when: groups['ipaclients']
|
||||
vars:
|
||||
ipa_test_host: ipaclients
|
||||
|
||||
- name: Test user certs using client context, in client host.
|
||||
ansible.builtin.import_playbook: test_cert_user.yml
|
||||
when: groups['ipaclients']
|
||||
vars:
|
||||
ipa_test_host: ipaclients
|
||||
|
||||
- name: Test host certs using client context, in server host.
|
||||
ansible.builtin.import_playbook: test_cert_host.yml
|
||||
when: groups['ipaclients'] is not defined or not groups['ipaclients']
|
||||
|
||||
- name: Test service certs using client context, in server host.
|
||||
ansible.builtin.import_playbook: test_cert_service.yml
|
||||
when: groups['ipaclients'] is not defined or not groups['ipaclients']
|
||||
|
||||
- name: Test user certs using client context, in server host.
|
||||
ansible.builtin.import_playbook: test_cert_user.yml
|
||||
when: groups['ipaclients'] is not defined or not groups['ipaclients']
|
||||
214
tests/cert/test_cert_host.yml
Normal file
214
tests/cert/test_cert_host.yml
Normal file
@@ -0,0 +1,214 @@
|
||||
---
|
||||
- name: Test host certificate requests
|
||||
hosts: "{{ ipa_test_host | default('ipaserver') }}"
|
||||
become: false
|
||||
gather_facts: false
|
||||
module_defaults:
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
# ipacert only supports client context
|
||||
ipaapi_context: "client"
|
||||
|
||||
tasks:
|
||||
|
||||
# SETUP
|
||||
|
||||
- name: Ensure test files do not exist
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
with_items:
|
||||
- "/root/retrieved.pem"
|
||||
- "/root/cert_1.pem"
|
||||
- "/root/host.csr"
|
||||
|
||||
# Ensure test items exist
|
||||
|
||||
- name: Ensure domain name is set
|
||||
ansible.builtin.set_fact:
|
||||
ipa_domain: ipa.test
|
||||
when: ipa_domain is not defined
|
||||
|
||||
- name: Ensure test host exists
|
||||
ipahost:
|
||||
name: "certhost.{{ ipa_domain }}"
|
||||
state: present
|
||||
force: true
|
||||
|
||||
- name: Create CSR
|
||||
ansible.builtin.shell:
|
||||
cmd: "openssl req -newkey rsa:1024 -keyout /dev/null -nodes -subj /CN=certhost.{{ ipa_domain }}"
|
||||
register: host_req
|
||||
|
||||
- name: Create CSR file
|
||||
ansible.builtin.copy:
|
||||
dest: "/root/host.csr"
|
||||
content: "{{ host_req.stdout }}"
|
||||
mode: 0644
|
||||
|
||||
# TESTS
|
||||
|
||||
- name: Request certificate for host
|
||||
ipacert:
|
||||
csr: '{{ host_req.stdout }}'
|
||||
principal: "host/certhost.{{ ipa_domain }}"
|
||||
state: requested
|
||||
register: host_cert
|
||||
failed_when: not host_cert.changed or host_cert.failed
|
||||
|
||||
- name: Display data from the requested certificate.
|
||||
ansible.builtin.debug:
|
||||
var: host_cert
|
||||
|
||||
- name: Retrieve certificate for host
|
||||
ipacert:
|
||||
serial_number: "{{ host_cert.certificate.serial_number }}"
|
||||
state: retrieved
|
||||
register: retrieved
|
||||
failed_when: retrieved.certificate.serial_number != host_cert.certificate.serial_number
|
||||
|
||||
- name: Display data from the retrieved certificate.
|
||||
ansible.builtin.debug:
|
||||
var: retrieved
|
||||
|
||||
- name: Place certificate on hold
|
||||
ipacert:
|
||||
serial_number: '{{ host_cert.certificate.serial_number }}'
|
||||
state: held
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Place certificate on hold, again
|
||||
ipacert:
|
||||
serial_number: '{{ host_cert.certificate.serial_number }}'
|
||||
state: held
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Release hold on certificate
|
||||
ipacert:
|
||||
serial_number: '{{ host_cert.certificate.serial_number }}'
|
||||
state: released
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Release hold on certificate, again
|
||||
ipacert:
|
||||
serial_number: '{{ host_cert.certificate.serial_number }}'
|
||||
state: released
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Revoke certificate
|
||||
ipacert:
|
||||
serial_number: '{{ host_cert.certificate.serial_number }}'
|
||||
state: revoked
|
||||
reason: keyCompromise
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Revoke certificate, again
|
||||
ipacert:
|
||||
serial_number: '{{ host_cert.certificate.serial_number }}'
|
||||
state: revoked
|
||||
reason: keyCompromise
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Try to revoke inexistent certificate
|
||||
ipacert:
|
||||
serial_number: 0x123456789
|
||||
reason: 9
|
||||
state: revoked
|
||||
register: result
|
||||
failed_when: not (result.failed and ("Request failed with status 404" in result.msg or "Certificate serial number 0x123456789 not found" in result.msg))
|
||||
|
||||
- name: Try to release revoked certificate
|
||||
ipacert:
|
||||
serial_number: '{{ host_cert.certificate.serial_number }}'
|
||||
state: released
|
||||
register: result
|
||||
failed_when: not result.failed or "Cannot release hold on certificate revoked with reason" not in result.msg
|
||||
|
||||
- name: Request certificate for host and save to file
|
||||
ipacert:
|
||||
csr: '{{ host_req.stdout }}'
|
||||
principal: "host/certhost.{{ ipa_domain }}"
|
||||
certificate_out: "/root/cert_1.pem"
|
||||
state: requested
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed or result.certificate
|
||||
|
||||
- name: Check requested certificate file
|
||||
ansible.builtin.file:
|
||||
path: "/root/cert_1.pem"
|
||||
check_mode: true
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Retrieve certificate for host to a file
|
||||
ipacert:
|
||||
serial_number: "{{ host_cert.certificate.serial_number }}"
|
||||
certificate_out: "/root/retrieved.pem"
|
||||
state: retrieved
|
||||
register: result
|
||||
failed_when: result.changed or result.failed or result.certificate
|
||||
|
||||
- name: Check retrieved certificate file
|
||||
ansible.builtin.file:
|
||||
path: "/root/retrieved.pem"
|
||||
check_mode: true
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Request with invalid CSR.
|
||||
ipacert:
|
||||
csr: |
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
BNxXqLcHylNEyg8SH0u63bWyxtgoDBfdZwdGAhYuJ+g4ev79J5eYoB0CAwEAAaAr
|
||||
MCkGCSqGSIb3DQEJDjEcMBowGAYHKoZIzlYIAQQNDAtoZWxsbyB3b3JsZDANBgkq
|
||||
hkiG9w0BAQsFAAOBgQADCi5BHDv1mrBFDWqYytFpQ1mrvr/mdax3AYXxNL2UEV8j
|
||||
AqZAFTEnJXL/u1eVQtI1yotqxakyUBN4XZBP2CBgJRO93Mtry8cgvU1sPdU8Mavx
|
||||
5gSnlP74Hio2ziscWWydlxpYxFx0gkKvu+0nyIpz954SVYwQ2wwk5FRqZnxI5w==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
principal: "host/certhost.{{ ipa_domain }}"
|
||||
state: requested
|
||||
register: result
|
||||
failed_when: not (result.failed and "Failure decoding Certificate Signing Request" in result.msg)
|
||||
|
||||
- name: Request certificate using a file
|
||||
ipacert:
|
||||
csr_file: "/root/host.csr"
|
||||
principal: "host/certhost.{{ ipa_domain }}"
|
||||
state: requested
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Request certificate using an invalid profile
|
||||
ipacert:
|
||||
csr_file: "/root/host.csr"
|
||||
principal: "host/certhost.{{ ipa_domain }}"
|
||||
profile: invalid_profile
|
||||
state: requested
|
||||
register: result
|
||||
failed_when: not (result.failed and "Request failed with status 400" in result.msg)
|
||||
|
||||
|
||||
# CLEANUP TEST ITEMS
|
||||
|
||||
- name: Removet test host
|
||||
ipahost:
|
||||
name: "certhost.{{ ipa_domain }}"
|
||||
state: absent
|
||||
|
||||
- name: Ensure test files do not exist
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
with_items:
|
||||
- "/root/retrieved.pem"
|
||||
- "/root/cert_1.pem"
|
||||
- "/root/host.csr"
|
||||
232
tests/cert/test_cert_service.yml
Normal file
232
tests/cert/test_cert_service.yml
Normal file
@@ -0,0 +1,232 @@
|
||||
---
|
||||
- name: Test service certificate requests
|
||||
hosts: "{{ ipa_test_host | default('ipaserver') }}"
|
||||
# Change "become" or "gather_facts" to "yes",
|
||||
# if you test playbook requires any.
|
||||
become: false
|
||||
gather_facts: false
|
||||
module_defaults:
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
# ipacert only supports client context
|
||||
ipaapi_context: "client"
|
||||
|
||||
tasks:
|
||||
|
||||
# SETUP
|
||||
|
||||
- name: Ensure test files do not exist
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
with_items:
|
||||
- "/root/retrieved.pem"
|
||||
- "/root/cert_1.pem"
|
||||
- "/root/service.csr"
|
||||
|
||||
# Ensure test items exist
|
||||
|
||||
- name: Ensure domain name is set
|
||||
ansible.builtin.set_fact:
|
||||
ipa_domain: ipa.test
|
||||
when: ipa_domain is not defined
|
||||
|
||||
- name: Ensure test host exist
|
||||
ipahost:
|
||||
name: "certservice.{{ ipa_domain }}"
|
||||
force: true
|
||||
state: present
|
||||
|
||||
- name: Ensure service exist
|
||||
ipaservice:
|
||||
name: "HTTP/certservice.{{ ipa_domain }}"
|
||||
force: true
|
||||
state: present
|
||||
|
||||
- name: Create signing request for certificate
|
||||
ansible.builtin.shell:
|
||||
cmd: "openssl req -newkey rsa:1024 -keyout /dev/null -nodes -subj /CN=certservice.{{ ipa_domain }}"
|
||||
register: service_req
|
||||
|
||||
- name: Create CSR file
|
||||
ansible.builtin.copy:
|
||||
dest: "/root/service.csr"
|
||||
content: "{{ service_req.stdout }}"
|
||||
mode: '0644'
|
||||
|
||||
# TESTS
|
||||
|
||||
- name: Request certificate for service
|
||||
ipacert:
|
||||
csr: '{{ service_req.stdout }}'
|
||||
principal: "HTTP/certservice.{{ ipa_domain }}"
|
||||
add_principal: true
|
||||
state: requested
|
||||
register: service_cert
|
||||
failed_when: not service_cert.changed or service_cert.failed
|
||||
|
||||
- name: Display data from the requested certificate.
|
||||
ansible.builtin.debug:
|
||||
var: service_cert
|
||||
|
||||
- name: Retrieve certificate for service
|
||||
ipacert:
|
||||
serial_number: "{{ service_cert.certificate.serial_number }}"
|
||||
state: retrieved
|
||||
register: retrieved
|
||||
failed_when: retrieved.certificate.serial_number != service_cert.certificate.serial_number
|
||||
|
||||
- name: Display data from the retrieved certificate.
|
||||
ansible.builtin.debug:
|
||||
var: retrieved
|
||||
|
||||
- name: Place certificate on hold
|
||||
ipacert:
|
||||
serial_number: '{{ service_cert.certificate.serial_number }}'
|
||||
state: held
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Place certificate on hold, again
|
||||
ipacert:
|
||||
serial_number: '{{ service_cert.certificate.serial_number }}'
|
||||
state: held
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Release hold on certificate
|
||||
ipacert:
|
||||
serial_number: '{{ service_cert.certificate.serial_number }}'
|
||||
state: released
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Release hold on certificate, again
|
||||
ipacert:
|
||||
serial_number: '{{ service_cert.certificate.serial_number }}'
|
||||
state: released
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Revoke certificate
|
||||
ipacert:
|
||||
serial_number: '{{ service_cert.certificate.serial_number }}'
|
||||
state: revoked
|
||||
reason: keyCompromise
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Revoke certificate, again
|
||||
ipacert:
|
||||
serial_number: '{{ service_cert.certificate.serial_number }}'
|
||||
state: revoked
|
||||
reason: keyCompromise
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Try to revoke inexistent certificate
|
||||
ipacert:
|
||||
serial_number: 0x123456789
|
||||
reason: 9
|
||||
state: revoked
|
||||
register: result
|
||||
failed_when: not (result.failed and ("Request failed with status 404" in result.msg or "Certificate serial number 0x123456789 not found" in result.msg))
|
||||
|
||||
- name: Try to release revoked certificate
|
||||
ipacert:
|
||||
serial_number: '{{ service_cert.certificate.serial_number }}'
|
||||
state: released
|
||||
register: result
|
||||
failed_when: not result.failed or "Cannot release hold on certificate revoked with reason" not in result.msg
|
||||
|
||||
- name: Request certificate for service and save to file
|
||||
ipacert:
|
||||
csr: '{{ service_req.stdout }}'
|
||||
principal: "HTTP/certservice.{{ ipa_domain }}"
|
||||
add_principal: true
|
||||
certificate_out: "/root/cert_1.pem"
|
||||
state: requested
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed or result.certificate
|
||||
|
||||
- name: Check requested certificate file
|
||||
ansible.builtin.file:
|
||||
path: "/root/cert_1.pem"
|
||||
check_mode: true
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Retrieve certificate for service to a file
|
||||
ipacert:
|
||||
serial_number: "{{ service_cert.certificate.serial_number }}"
|
||||
certificate_out: "/root/retrieved.pem"
|
||||
state: retrieved
|
||||
register: result
|
||||
failed_when: result.changed or result.failed or result.certificate
|
||||
|
||||
- name: Check retrieved certificate file
|
||||
ansible.builtin.file:
|
||||
path: "/root/retrieved.pem"
|
||||
check_mode: true
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Request with invalid CSR.
|
||||
ipacert:
|
||||
csr: |
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
BNxXqLcHylNEyg8SH0u63bWyxtgoDBfdZwdGAhYuJ+g4ev79J5eYoB0CAwEAAaAr
|
||||
MCkGCSqGSIb3DQEJDjEcMBowGAYHKoZIzlYIAQQNDAtoZWxsbyB3b3JsZDANBgkq
|
||||
hkiG9w0BAQsFAAOBgQADCi5BHDv1mrBFDWqYytFpQ1mrvr/mdax3AYXxNL2UEV8j
|
||||
AqZAFTEnJXL/u1eVQtI1yotqxakyUBN4XZBP2CBgJRO93Mtry8cgvU1sPdU8Mavx
|
||||
5gSnlP74Hio2ziscWWydlxpYxFx0gkKvu+0nyIpz954SVYwQ2wwk5FRqZnxI5w==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
principal: "HTTP/certservice.{{ ipa_domain }}"
|
||||
state: requested
|
||||
register: result
|
||||
failed_when: not (result.failed and "Failure decoding Certificate Signing Request" in result.msg)
|
||||
|
||||
- name: Request certificate using a file
|
||||
ipacert:
|
||||
csr_file: "/root/service.csr"
|
||||
principal: "HTTP/certservice.{{ ipa_domain }}"
|
||||
state: requested
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Request certificate using an invalid profile
|
||||
ipacert:
|
||||
csr_file: "/root/service.csr"
|
||||
principal: "HTTP/certservice.{{ ipa_domain }}"
|
||||
profile: invalid_profile
|
||||
state: requested
|
||||
register: result
|
||||
failed_when: not (result.failed and "Request failed with status 400" in result.msg)
|
||||
|
||||
# CLEANUP TEST ITEMS
|
||||
|
||||
- name: Remove test service
|
||||
ipaservice:
|
||||
name: "HTTP/certservice.{{ ipa_domain }}"
|
||||
state: absent
|
||||
continue: true
|
||||
|
||||
- name: Remove test host
|
||||
ipahost:
|
||||
name: certservice.example.com
|
||||
state: absent
|
||||
|
||||
- name: Ensure test files do not exist
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
with_items:
|
||||
- "/root/retrieved.pem"
|
||||
- "/root/cert_1.pem"
|
||||
- "/root/service.csr"
|
||||
213
tests/cert/test_cert_user.yml
Normal file
213
tests/cert/test_cert_user.yml
Normal file
@@ -0,0 +1,213 @@
|
||||
---
|
||||
- name: Test user certificate requests
|
||||
hosts: "{{ ipa_test_host | default('ipaserver') }}"
|
||||
become: false
|
||||
gather_facts: false
|
||||
module_defaults:
|
||||
ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
ipacert:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
# ipacert only supports client context
|
||||
ipaapi_context: "client"
|
||||
|
||||
tasks:
|
||||
|
||||
# Ensure test files do not exist
|
||||
|
||||
- name: Check retrieved certificate file
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
with_items:
|
||||
- "/root/retrieved.pem"
|
||||
- "/root/cert_1.pem"
|
||||
- "/root/user.csr"
|
||||
|
||||
# Ensure test items exist.
|
||||
|
||||
- name: Ensure test user exists
|
||||
ipauser:
|
||||
name: certuser
|
||||
first: certificate
|
||||
last: user
|
||||
|
||||
- name: Crete CSR
|
||||
ansible.builtin.shell:
|
||||
cmd:
|
||||
'openssl req -newkey rsa:1024 -keyout /dev/null -nodes -subj /CN=certuser -reqexts IECUserRoles
|
||||
-config <(cat /etc/pki/tls/openssl.cnf; printf "[IECUserRoles]\n1.2.840.10070.8.1=ASN1:UTF8String:hello world")'
|
||||
executable: /bin/bash
|
||||
register: user_req
|
||||
|
||||
- name: Create CSR file
|
||||
ansible.builtin.copy:
|
||||
dest: "/root/user.csr"
|
||||
content: "{{ user_req.stdout }}"
|
||||
mode: 0644
|
||||
|
||||
# TESTS
|
||||
|
||||
- name: Request certificate for user
|
||||
ipacert:
|
||||
csr: '{{ user_req.stdout }}'
|
||||
principal: certuser
|
||||
profile: IECUserRoles
|
||||
state: requested
|
||||
register: user_cert
|
||||
failed_when: not user_cert.changed or user_cert.failed
|
||||
|
||||
- name: Display data from the requested certificate.
|
||||
ansible.builtin.debug:
|
||||
var: user_cert
|
||||
|
||||
- name: Retrieve certificate for user
|
||||
ipacert:
|
||||
serial_number: "{{ user_cert.certificate.serial_number }}"
|
||||
state: retrieved
|
||||
register: retrieved
|
||||
failed_when: retrieved.certificate.serial_number != user_cert.certificate.serial_number
|
||||
|
||||
- name: Display data from the retrieved certificate.
|
||||
ansible.builtin.debug:
|
||||
var: retrieved
|
||||
|
||||
- name: Place certificate on hold
|
||||
ipacert:
|
||||
serial_number: '{{ user_cert.certificate.serial_number }}'
|
||||
state: held
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Place certificate on hold, again
|
||||
ipacert:
|
||||
serial_number: '{{ user_cert.certificate.serial_number }}'
|
||||
state: held
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Release hold on certificate
|
||||
ipacert:
|
||||
serial_number: '{{ user_cert.certificate.serial_number }}'
|
||||
state: released
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Release hold on certificate, again
|
||||
ipacert:
|
||||
serial_number: '{{ user_cert.certificate.serial_number }}'
|
||||
state: released
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Revoke certificate
|
||||
ipacert:
|
||||
serial_number: '{{ user_cert.certificate.serial_number }}'
|
||||
state: revoked
|
||||
reason: keyCompromise
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Revoke certificate, again
|
||||
ipacert:
|
||||
serial_number: '{{ user_cert.certificate.serial_number }}'
|
||||
state: revoked
|
||||
reason: keyCompromise
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Try to revoke inexistent certificate
|
||||
ipacert:
|
||||
serial_number: 0x123456789
|
||||
reason: 9
|
||||
state: revoked
|
||||
register: result
|
||||
failed_when: not (result.failed and ("Request failed with status 404" in result.msg or "Certificate serial number 0x123456789 not found" in result.msg))
|
||||
|
||||
- name: Try to release revoked certificate
|
||||
ipacert:
|
||||
serial_number: '{{ user_cert.certificate.serial_number }}'
|
||||
state: released
|
||||
register: result
|
||||
failed_when: not result.failed or "Cannot release hold on certificate revoked with reason" not in result.msg
|
||||
|
||||
- name: Request certificate for user and save to file
|
||||
ipacert:
|
||||
csr: '{{ user_req.stdout }}'
|
||||
principal: certuser
|
||||
profile: IECUserRoles
|
||||
certificate_out: "/root/cert_1.pem"
|
||||
state: requested
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed or result.certificate
|
||||
|
||||
- name: Check requested certificate file
|
||||
ansible.builtin.file:
|
||||
path: "/root/cert_1.pem"
|
||||
check_mode: true
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Retrieve certificate for user to a file
|
||||
ipacert:
|
||||
serial_number: "{{ user_cert.certificate.serial_number }}"
|
||||
certificate_out: "/root/retrieved.pem"
|
||||
state: retrieved
|
||||
register: result
|
||||
failed_when: result.changed or result.failed or result.certificate
|
||||
|
||||
- name: Check retrieved certificate file
|
||||
ansible.builtin.file:
|
||||
path: "/root/retrieved.pem"
|
||||
check_mode: true
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Request with invalid CSR.
|
||||
ipacert:
|
||||
csr: |
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
BNxXqLcHylNEyg8SH0u63bWyxtgoDBfdZwdGAhYuJ+g4ev79J5eYoB0CAwEAAaAr
|
||||
MCkGCSqGSIb3DQEJDjEcMBowGAYHKoZIzlYIAQQNDAtoZWxsbyB3b3JsZDANBgkq
|
||||
hkiG9w0BAQsFAAOBgQADCi5BHDv1mrBFDWqYytFpQ1mrvr/mdax3AYXxNL2UEV8j
|
||||
AqZAFTEnJXL/u1eVQtI1yotqxakyUBN4XZBP2CBgJRO93Mtry8cgvU1sPdU8Mavx
|
||||
5gSnlP74Hio2ziscWWydlxpYxFx0gkKvu+0nyIpz954SVYwQ2wwk5FRqZnxI5w==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
principal: certuser
|
||||
state: requested
|
||||
register: result
|
||||
failed_when: not (result.failed and "Failure decoding Certificate Signing Request" in result.msg)
|
||||
|
||||
- name: Request certificate using a file
|
||||
ipacert:
|
||||
csr_file: "/root/user.csr"
|
||||
principal: certuser
|
||||
state: requested
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Request certificate using an invalid profile
|
||||
ipacert:
|
||||
csr_file: "/root/user.csr"
|
||||
principal: certuser
|
||||
profile: invalid_profile
|
||||
state: requested
|
||||
register: result
|
||||
failed_when: not (result.failed and "Request failed with status 400" in result.msg)
|
||||
|
||||
# CLEANUP TEST ITEMS
|
||||
|
||||
- name: Remove test user
|
||||
ipauser:
|
||||
name: certuser
|
||||
state: absent
|
||||
|
||||
- name: Check retrieved certificate file
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
with_items:
|
||||
- "/root/retrieved.pem"
|
||||
- "/root/cert_1.pem"
|
||||
- "/root/user.csr"
|
||||
@@ -59,13 +59,13 @@
|
||||
pac_type: ""
|
||||
|
||||
- name: Execute tests if ipa_version >= 4.8.0
|
||||
when: ipa_version is version('4.8.0', '>=')
|
||||
block:
|
||||
- name: Set maxhostname to 255
|
||||
ipaconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
maxhostname: 255
|
||||
when: ipa_version is version('4.8.0', '>=')
|
||||
|
||||
- name: Set maxusername to 45
|
||||
ipaconfig:
|
||||
@@ -225,6 +225,7 @@
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Execute tests if ipa_version >= 4.8.0
|
||||
when: ipa_version is version('4.8.0', '>=')
|
||||
block:
|
||||
- name: Set maxhostname to 77
|
||||
ipaconfig:
|
||||
@@ -241,7 +242,6 @@
|
||||
maxhostname: 77
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
when: ipa_version is version('4.8.0', '>=')
|
||||
|
||||
- name: Set pwdexpnotify to 17
|
||||
ipaconfig:
|
||||
@@ -415,13 +415,13 @@
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Execute tests if ipa_version >= 4.8.0
|
||||
when: ipa_version is version('4.8.0', '>=')
|
||||
block:
|
||||
- name: Reset maxhostname
|
||||
ipaconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
maxhostname: '{{ previousconfig.config.maxhostname | default(omit) }}'
|
||||
when: ipa_version is version('4.8.0', '>=')
|
||||
|
||||
- name: Reset changed fields, again
|
||||
ipaconfig:
|
||||
@@ -451,13 +451,13 @@
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Execute tests if ipa_version >= 4.8.0
|
||||
when: ipa_version is version('4.8.0', '>=')
|
||||
block:
|
||||
- name: Reset maxhostname
|
||||
ipaconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
maxhostname: '{{ previousconfig.config.maxhostname | default(omit) }}'
|
||||
when: ipa_version is version('4.8.0', '>=')
|
||||
|
||||
rescue:
|
||||
- name: Set fields to IPA default, due to error
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
# TESTS
|
||||
- name: Test config sid
|
||||
# only run tests if version supports enable-sid
|
||||
when: ipa_version is version("4.9.8", ">=")
|
||||
block:
|
||||
- name: Check if SID is enabled.
|
||||
ipaconfig:
|
||||
@@ -28,7 +30,7 @@
|
||||
check_mode: yes
|
||||
register: sid_disabled
|
||||
|
||||
- name: Ensure netbios_name can't be changed without SID enabled.
|
||||
- name: Ensure netbios_name can't be changed without SID enabled. # noqa 503
|
||||
ipaconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
@@ -37,7 +39,7 @@
|
||||
failed_when: not result.failed and "SID generation must be enabled" in result.msg
|
||||
when: sid_disabled.changed
|
||||
|
||||
- name: Ensure SIDs can't be changed without SID enabled.
|
||||
- name: Ensure SIDs can't be changed without SID enabled. # noqa 503
|
||||
ipaconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
@@ -115,8 +117,6 @@
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
add_sids: yes
|
||||
|
||||
# only run tests if version supports enable-sid
|
||||
when: ipa_version is version("4.9.8", ">=")
|
||||
# REVERT TO PREVIOUS CONFIG
|
||||
always:
|
||||
# Once SID is enabled, it cannot be reverted.
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
trust_test_is_supported: no
|
||||
|
||||
- name: Ensure ipaserver_domain is set
|
||||
when: ipaserver_domain is not defined
|
||||
block:
|
||||
- name: Get Domain from server name
|
||||
ansible.builtin.set_fact:
|
||||
@@ -41,4 +42,3 @@
|
||||
ansible.builtin.set_fact:
|
||||
ipaserver_domain: "ipa.test"
|
||||
when: "'fqdn' not in ansible_facts"
|
||||
when: ipaserver_domain is not defined
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/bash -eu
|
||||
|
||||
master=$1
|
||||
if [ -z "$master" ]; then
|
||||
|
||||
13
tests/group/create_groups_json.yml
Normal file
13
tests/group/create_groups_json.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
- name: Create groups.json
|
||||
hosts: localhost
|
||||
|
||||
tasks:
|
||||
- name: Check if groups.json exists
|
||||
ansible.builtin.stat:
|
||||
path: groups.json
|
||||
register: register_stat_groups
|
||||
|
||||
- name: Create groups.json
|
||||
ansible.builtin.command: /bin/bash groups.sh 500
|
||||
when: not register_stat_groups.stat.exists
|
||||
25
tests/group/groups.sh
Normal file
25
tests/group/groups.sh
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
NUM=${1-1000}
|
||||
FILE="groups.json"
|
||||
|
||||
echo "{" > "$FILE"
|
||||
|
||||
echo " \"group_list\": [" >> "$FILE"
|
||||
|
||||
for i in $(seq 1 "$NUM"); do
|
||||
{
|
||||
echo " {"
|
||||
echo " \"name\": \"group$i\","
|
||||
echo " \"description\": \"group description $i\""
|
||||
} >> "$FILE"
|
||||
if [ "$i" -lt "$NUM" ]; then
|
||||
echo " }," >> "$FILE"
|
||||
else
|
||||
echo " }" >> "$FILE"
|
||||
fi
|
||||
done
|
||||
|
||||
echo " ]" >> "$FILE"
|
||||
|
||||
echo "}" >> "$FILE"
|
||||
@@ -138,6 +138,7 @@
|
||||
# service
|
||||
|
||||
- name: Execute tests if ipa_verison >= 4.7.0
|
||||
when: ipa_version is version('4.7.0', '>=')
|
||||
block:
|
||||
|
||||
- name: Ensure service "{{ 'HTTP/' + fqdn_at_domain }}" is present in group group1
|
||||
@@ -282,8 +283,6 @@
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
when: ipa_version is version('4.7.0', '>=')
|
||||
|
||||
# user
|
||||
|
||||
- name: Ensure users user1, user2 and user3 are present in group group1
|
||||
|
||||
@@ -37,3 +37,27 @@
|
||||
when: groups['ipaclients'] is not defined or not groups['ipaclients']
|
||||
vars:
|
||||
ipa_context: client
|
||||
|
||||
- name: Test groups using client context, in client host.
|
||||
ansible.builtin.import_playbook: test_groups.yml
|
||||
when: groups['ipaclients']
|
||||
vars:
|
||||
ipa_test_host: ipaclients
|
||||
|
||||
- name: Test groups using client context, in server host.
|
||||
ansible.builtin.import_playbook: test_groups.yml
|
||||
when: groups['ipaclients'] is not defined or not groups['ipaclients']
|
||||
vars:
|
||||
ipa_context: client
|
||||
|
||||
- name: Test groups with mixed types using client context, in client host.
|
||||
ansible.builtin.import_playbook: test_groups_external_nonposix.yml
|
||||
when: groups['ipaclients']
|
||||
vars:
|
||||
ipa_test_host: ipaclients
|
||||
|
||||
- name: Test groups with mixed types using client context, in server host.
|
||||
ansible.builtin.import_playbook: test_groups_external_nonposix.yml
|
||||
when: groups['ipaclients'] is not defined or not groups['ipaclients']
|
||||
vars:
|
||||
ipa_context: client
|
||||
|
||||
81
tests/group/test_group_external_group_members_no_trust.yml
Normal file
81
tests/group/test_group_external_group_members_no_trust.yml
Normal file
@@ -0,0 +1,81 @@
|
||||
---
|
||||
- name: Test external group group members (without trust-ad installed)
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Ensure external test groups are absent
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name:
|
||||
- externaltestgroup01
|
||||
- externaltestgroup02
|
||||
state: absent
|
||||
|
||||
- name: Create external test group 01
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: externaltestgroup01
|
||||
external: true
|
||||
register: result
|
||||
failed_when: result.failed or not result.changed
|
||||
|
||||
- name: Create external test group 02
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: externaltestgroup02
|
||||
external: true
|
||||
register: result
|
||||
failed_when: result.failed or not result.changed
|
||||
|
||||
- name: Ensure externaltestgroup02 is a member of externaltestgroup01
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: externaltestgroup01
|
||||
action: member
|
||||
group:
|
||||
- externaltestgroup02
|
||||
register: result
|
||||
failed_when: result.failed or not result.changed
|
||||
|
||||
- name: Ensure externaltestgroup02 is a member of externaltestgroup01, again
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: externaltestgroup01
|
||||
action: member
|
||||
group:
|
||||
- externaltestgroup02
|
||||
register: result
|
||||
failed_when: result.failed or result.changed
|
||||
|
||||
- name: Ensure externaltestgroup02 is not a member of externaltestgroup01
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: externaltestgroup01
|
||||
action: member
|
||||
group:
|
||||
- externaltestgroup02
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: result.failed or not result.changed
|
||||
|
||||
- name: Ensure externaltestgroup02 is not a member of externaltestgroup01, again
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: externaltestgroup01
|
||||
action: member
|
||||
group:
|
||||
- externaltestgroup02
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: result.failed or result.changed
|
||||
|
||||
- name: Ensure external test groups are absent
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name:
|
||||
- externaltestgroup01
|
||||
- externaltestgroup02
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: result.failed or not result.changed
|
||||
@@ -10,6 +10,7 @@
|
||||
ansible.builtin.include_tasks: ../env_freeipa_facts.yml
|
||||
|
||||
- name: Execute group tests if trust test environment is supported
|
||||
when: trust_test_is_supported | default(false)
|
||||
block:
|
||||
|
||||
- name: Add nonposix group.
|
||||
@@ -111,5 +112,3 @@
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: extgroup
|
||||
state: absent
|
||||
|
||||
when: trust_test_is_supported | default(false)
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
# EXTERNAL MEMBER TEST (REQUIRES AD)
|
||||
|
||||
- name: Execute group tests if trust test environment is supported
|
||||
when: trust_test_is_supported | default(false)
|
||||
block:
|
||||
|
||||
- name: Ensure users testuser1, testuser2 and testuser3 are present in group externalgroup
|
||||
@@ -231,8 +232,6 @@
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
when: trust_test_is_supported | default(false)
|
||||
|
||||
# CONVERT NONPOSIX TO POSIX GROUP WITH USERS
|
||||
|
||||
- name: Ensure nonposix group nonposixgroup as posix
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
ansible.builtin.include_tasks: ../env_freeipa_facts.yml
|
||||
|
||||
- name: Execute tests if ipa_verison >= 4.8.7 and trust test environment is supported
|
||||
when: ipa_version is version("4.8.7", ">=") and trust_test_is_supported | default(false)
|
||||
block:
|
||||
- name: Create idoverrideuser.
|
||||
ansible.builtin.shell: |
|
||||
@@ -97,10 +98,8 @@
|
||||
|
||||
always:
|
||||
- name: Remove idoverrideuser.
|
||||
ansible.builtin.shell: |
|
||||
kinit -c idoverride_cache admin <<< SomeADMINpassword
|
||||
ipa idoverrideuser-del "Default Trust View" {{ ad_user }}
|
||||
kdestroy -A -q -c idoverride_cache
|
||||
when:
|
||||
|
||||
when: ipa_version is version("4.8.7", ">=") and trust_test_is_supported | default(false)
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
kinit -c idoverride_cache admin <<< SomeADMINpassword
|
||||
ipa idoverrideuser-del "Default Trust View" {{ ad_user }}
|
||||
kdestroy -A -q -c idoverride_cache
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
ansible.builtin.include_tasks: ../env_freeipa_facts.yml
|
||||
|
||||
- name: Execute tests if ipa_verison >= 4.8.4
|
||||
when: ipa_version is version('4.8.4', '>=')
|
||||
block:
|
||||
- name: Ensure user manangeruser1 and manageruser2 is absent
|
||||
ipauser:
|
||||
@@ -206,5 +207,3 @@
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
when: ipa_version is version('4.8.4', '>=')
|
||||
|
||||
143
tests/group/test_groups.yml
Normal file
143
tests/group/test_groups.yml
Normal file
@@ -0,0 +1,143 @@
|
||||
---
|
||||
- name: Test groups
|
||||
hosts: "{{ ipa_test_host | default('ipaserver') }}"
|
||||
gather_facts: true
|
||||
|
||||
tasks:
|
||||
# setup
|
||||
- name: Include tasks ../env_freeipa_facts.yml
|
||||
ansible.builtin.include_tasks: ../env_freeipa_facts.yml
|
||||
|
||||
# GET FQDN_AT_DOMAIN
|
||||
|
||||
- name: Get fqdn_at_domain
|
||||
ansible.builtin.set_fact:
|
||||
fqdn_at_domain: "{{ ansible_facts['fqdn'] + '@' + ipaserver_realm }}"
|
||||
|
||||
# CLEANUP TEST ITEMS
|
||||
|
||||
- name: Remove test groups
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: group1,group2,group3,group4,group5,group6,group7,group8,group9,group10
|
||||
state: absent
|
||||
|
||||
- name: Remove test users
|
||||
ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: user1,user2,user3
|
||||
state: absent
|
||||
|
||||
# CREATE TEST ITEMS
|
||||
|
||||
- name: Users user1..3 present
|
||||
ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
users:
|
||||
- name: user1
|
||||
first: user1
|
||||
last: Last
|
||||
- name: user2
|
||||
first: user2
|
||||
last: Last
|
||||
- name: user3
|
||||
first: user3
|
||||
last: Last
|
||||
|
||||
# TESTS
|
||||
|
||||
- name: Groups group1..10 present
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: group1
|
||||
- name: group2
|
||||
user:
|
||||
- user1
|
||||
- user2
|
||||
- user3
|
||||
- name: group3
|
||||
group:
|
||||
- group1
|
||||
- group2
|
||||
- name: group4
|
||||
- name: group5
|
||||
- name: group6
|
||||
- name: group7
|
||||
- name: group8
|
||||
- name: group9
|
||||
- name: group10
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Groups group1..10 present again
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: group1
|
||||
- name: group2
|
||||
- name: group3
|
||||
- name: group4
|
||||
- name: group5
|
||||
- name: group6
|
||||
- name: group7
|
||||
- name: group8
|
||||
- name: group9
|
||||
- name: group10
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
# failed_when: not result.failed has been added as this test needs to
|
||||
# fail because two groups with the same name should be added in the same
|
||||
# task.
|
||||
- name: Duplicate names in groups failure test
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: group1
|
||||
- name: group2
|
||||
- name: group3
|
||||
- name: group3
|
||||
register: result
|
||||
failed_when: result.changed or not result.failed or "is used more than once" not in result.msg
|
||||
|
||||
- name: Groups/name and name group11 present
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: group11
|
||||
groups:
|
||||
- name: group11
|
||||
register: result
|
||||
failed_when: result.changed or not result.failed or "parameters are mutually exclusive" not in result.msg
|
||||
|
||||
- name: Groups/name and name are absent
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
register: result
|
||||
failed_when: result.changed or not result.failed or "one of the following is required" not in result.msg
|
||||
|
||||
- name: Name is absent
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name:
|
||||
register: result
|
||||
failed_when: result.changed or not result.failed or "At least one name or groups is required" not in result.msg
|
||||
|
||||
- name: Only one group can be added at a time using name.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: group11,group12
|
||||
register: result
|
||||
failed_when: result.changed or not result.failed or "Only one group can be added at a time using 'name'." not in result.msg
|
||||
|
||||
- name: Remove test groups
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: group1,group2,group3,group4,group5,group6,group7,group8,group9,group10
|
||||
state: absent
|
||||
|
||||
- name: Remove test users
|
||||
ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: user1,user2,user3
|
||||
state: absent
|
||||
35
tests/group/test_groups_absent.yml
Normal file
35
tests/group/test_groups_absent.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
- name: Include create_groups_json.yml
|
||||
ansible.builtin.import_playbook: create_groups_json.yml
|
||||
|
||||
- name: Test groups absent
|
||||
hosts: ipaserver
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: Include groups.json
|
||||
ansible.builtin.include_vars:
|
||||
file: groups.json # noqa 505
|
||||
|
||||
- name: Initialize groups_names
|
||||
ansible.builtin.set_fact:
|
||||
groups_names: []
|
||||
|
||||
- name: Create dict with group names
|
||||
ansible.builtin.set_fact:
|
||||
groups_names: "{{ groups_names | default([]) + [{'name': item.name}] }}"
|
||||
loop: "{{ group_list }}"
|
||||
|
||||
- name: Groups absent len:{{ group_list | length }}
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups: "{{ groups_names }}"
|
||||
state: absent
|
||||
|
||||
- name: Remove groups.json
|
||||
hosts: localhost
|
||||
tasks:
|
||||
- name: Remove groups.json
|
||||
ansible.builtin.file:
|
||||
state: absent
|
||||
path: groups.json
|
||||
395
tests/group/test_groups_external_nonposix.yml
Normal file
395
tests/group/test_groups_external_nonposix.yml
Normal file
@@ -0,0 +1,395 @@
|
||||
---
|
||||
- name: Test multiple external and nonposix groups
|
||||
hosts: "{{ ipa_test_host | default('ipaserver') }}"
|
||||
gather_facts: true
|
||||
|
||||
tasks:
|
||||
# setup
|
||||
- name: Include tasks ../env_freeipa_facts.yml
|
||||
ansible.builtin.include_tasks: ../env_freeipa_facts.yml
|
||||
|
||||
# GET FQDN_AT_DOMAIN
|
||||
|
||||
- name: Get fqdn_at_domain
|
||||
ansible.builtin.set_fact:
|
||||
fqdn_at_domain: "{{ ansible_facts['fqdn'] + '@' + ipaserver_realm }}"
|
||||
|
||||
# CLEANUP TEST ITEMS
|
||||
|
||||
- name: Remove testing groups.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name:
|
||||
- extgroup
|
||||
- nonposixgroup
|
||||
- posixgroup
|
||||
- fail_group
|
||||
- group_1
|
||||
- posix_group_1
|
||||
- nonposix_group_1
|
||||
- external_group_1
|
||||
- external_group_2
|
||||
state: absent
|
||||
|
||||
- name: Ensure test users testuser1, testuser2 and testuser3 are absent
|
||||
ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testuser1,testuser2,testuser3
|
||||
state: absent
|
||||
|
||||
# CREATE TEST ITEMS
|
||||
|
||||
- name: Ensure test users testuser1..testuser3 are present
|
||||
ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
users:
|
||||
- name: testuser1
|
||||
first: testuser1
|
||||
last: Last
|
||||
- name: testuser2
|
||||
first: testuser2
|
||||
last: Last
|
||||
- name: testuser3
|
||||
first: testuser3
|
||||
last: Last
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Add nonposix group.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: extgroup
|
||||
nonposix: true
|
||||
register: result
|
||||
failed_when: result.failed or not result.changed
|
||||
|
||||
- name: Add nonposix group, again.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: extgroup
|
||||
nonposix: true
|
||||
register: result
|
||||
failed_when: result.failed or result.changed
|
||||
|
||||
- name: Set group to be external
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: extgroup
|
||||
external: true
|
||||
register: result
|
||||
failed_when: result.failed or not result.changed
|
||||
|
||||
- name: Set group to be external, again.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: extgroup
|
||||
external: true
|
||||
register: result
|
||||
failed_when: result.failed or result.changed
|
||||
|
||||
- name: Set external group to be non-external.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: extgroup
|
||||
external: false
|
||||
register: result
|
||||
failed_when: not result.failed or "group can not be non-external" not in result.msg
|
||||
|
||||
- name: Set external group to be posix.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: extgroup
|
||||
posix: true
|
||||
register: result
|
||||
failed_when: not result.failed or "Cannot change `external` group" not in result.msg
|
||||
|
||||
- name: Add nonposix group.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: posixgroup
|
||||
nonposix: true
|
||||
register: result
|
||||
failed_when: result.failed or not result.changed
|
||||
|
||||
- name: Set group to be posix
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: posixgroup
|
||||
posix: true
|
||||
register: result
|
||||
failed_when: result.failed or not result.changed
|
||||
|
||||
- name: Set group to be posix, again.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: posixgroup
|
||||
posix: true
|
||||
register: result
|
||||
failed_when: result.failed or result.changed
|
||||
|
||||
- name: Set posix group to be external.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: posixgroup
|
||||
external: true
|
||||
register: result
|
||||
failed_when: not result.failed or "Cannot change `posix` group" not in result.msg
|
||||
|
||||
- name: Set posix group to be non-posix.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: posixgroup
|
||||
posix: false
|
||||
register: result
|
||||
failed_when: not result.failed or "Cannot change `posix` group" not in result.msg
|
||||
|
||||
- name: Set posix group to be non-posix.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: posixgroup
|
||||
nonposix: true
|
||||
register: result
|
||||
failed_when: not result.failed or "Cannot change `posix` group" not in result.msg
|
||||
|
||||
- name: Add nonposix group.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: nonposixgroup
|
||||
posix: false
|
||||
register: result
|
||||
failed_when: result.failed or not result.changed
|
||||
|
||||
- name: Add nonposix group, again.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: nonposixgroup
|
||||
nonposix: true
|
||||
register: result
|
||||
failed_when: result.failed or result.changed
|
||||
|
||||
|
||||
# NONPOSIX MEMBER TEST
|
||||
|
||||
- name: Ensure users testuser1, testuser2 and testuser3 are present in group nonposixgroup
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: nonposixgroup
|
||||
nonposix: true
|
||||
user:
|
||||
- testuser1
|
||||
- testuser2
|
||||
- testuser3
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Ensure users testuser1, testuser2 and testuser3 are present in group nonposixgroup again
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: nonposixgroup
|
||||
nonposix: true
|
||||
user:
|
||||
- testuser1
|
||||
- testuser2
|
||||
- testuser3
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
|
||||
# POSIX MEMBER TEST
|
||||
|
||||
- name: Ensure users testuser1, testuser2 and testuser3 are present in group posixgroup
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: posixgroup
|
||||
posix: true
|
||||
user:
|
||||
- testuser1
|
||||
- testuser2
|
||||
- testuser3
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Ensure users testuser1, testuser2 and testuser3 are present in group posixgroup again
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: posixgroup
|
||||
posix: true
|
||||
user:
|
||||
- testuser1
|
||||
- testuser2
|
||||
- testuser3
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
# EXTERNAL MEMBER TEST (REQUIRES AD)
|
||||
|
||||
- name: Execute group tests if trust test environment is supported
|
||||
when: trust_test_is_supported | default(false)
|
||||
block:
|
||||
- name: Ensure users testuser1, testuser2 and testuser3 are present in group externalgroup
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: externalgroup
|
||||
external: true
|
||||
user:
|
||||
- testuser1
|
||||
- testuser2
|
||||
- testuser3
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Ensure users testuser1, testuser2 and testuser3 are present in group externalgroup again
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: externalgroup
|
||||
external: true
|
||||
user:
|
||||
- testuser1
|
||||
- testuser2
|
||||
- testuser3
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
|
||||
# CONVERT NONPOSIX TO POSIX GROUP WITH USERS
|
||||
|
||||
- name: Ensure nonposix group nonposixgroup as posix
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: nonposixgroup
|
||||
posix: true
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Ensure nonposix group nonposixgroup as posix, again
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: nonposixgroup
|
||||
posix: true
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Ensure nonposix group nonposixgroup (now posix) has users still
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: nonposixgroup
|
||||
posix: true
|
||||
user:
|
||||
- testuser1
|
||||
- testuser2
|
||||
- testuser3
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
# FAIL ON COMBINATIONS OF NONPOSIX, POSIX AND EXTERNAL
|
||||
|
||||
- name: Fail to ensure group as nonposix and posix
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: firstgroup
|
||||
nonposix: true
|
||||
posix: false
|
||||
- name: fail_group
|
||||
nonposix: true
|
||||
posix: true
|
||||
register: result
|
||||
failed_when: not result.failed or "parameters are mutually exclusive for group `fail_group`" not in result.msg
|
||||
|
||||
- name: Fail to ensure group as nonposix and external
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: firstgroup
|
||||
nonposix: true
|
||||
posix: false
|
||||
- name: fail_group
|
||||
nonposix: true
|
||||
external: true
|
||||
register: result
|
||||
failed_when: not result.failed or "parameters are mutually exclusive for group `fail_group`" not in result.msg
|
||||
|
||||
- name: Fail to ensure group as posix and external
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: firstgroup
|
||||
nonposix: true
|
||||
posix: false
|
||||
- name: fail_group
|
||||
posix: true
|
||||
external: true
|
||||
register: result
|
||||
failed_when: not result.failed or "parameters are mutually exclusive for group `fail_group`" not in result.msg
|
||||
|
||||
# GROUPS WITH MIXED TYPES
|
||||
|
||||
- name: Adding posix, nonposix and external groups in one batch
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: posix_group_1
|
||||
posix: true
|
||||
- name: nonposix_group_1
|
||||
nonposix: true
|
||||
- name: external_group_1
|
||||
external: true
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Adding non-external and external groups in one batch
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups:
|
||||
- name: non_external_group_2
|
||||
- name: external_group_2
|
||||
external: true
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
# CLEANUP
|
||||
|
||||
- name: Remove testing groups.
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name:
|
||||
- extgroup
|
||||
- nonposixgroup
|
||||
- posixgroup
|
||||
- fail_group
|
||||
- group_1
|
||||
- posix_group_1
|
||||
- nonposix_group_1
|
||||
- external_group_1
|
||||
- external_group_2
|
||||
- non_external_group_2
|
||||
state: absent
|
||||
|
||||
- name: Ensure test users testuser1, testuser2 and testuser3 are absent
|
||||
ipauser:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testuser1,testuser2,testuser3
|
||||
state: absent
|
||||
40
tests/group/test_groups_present.yml
Normal file
40
tests/group/test_groups_present.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
- name: Include create_groups_json.yml
|
||||
ansible.builtin.import_playbook: create_groups_json.yml
|
||||
|
||||
- name: Test groups present
|
||||
hosts: ipaserver
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: Include groups.json
|
||||
ansible.builtin.include_vars:
|
||||
file: groups.json # noqa 505
|
||||
|
||||
- name: Groups present len:{{ group_list | length }}
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups: "{{ group_list }}"
|
||||
|
||||
- name: Initialize groups_names
|
||||
ansible.builtin.set_fact:
|
||||
groups_names: []
|
||||
|
||||
- name: Create dict with group names
|
||||
ansible.builtin.set_fact:
|
||||
groups_names: "{{ groups_names | default([]) + [{'name': item.name}] }}"
|
||||
loop: "{{ group_list }}"
|
||||
|
||||
- name: Remove groups
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups: "{{ groups_names }}"
|
||||
state: absent
|
||||
|
||||
- name: Remove groups.json
|
||||
hosts: localhost
|
||||
tasks:
|
||||
- name: Remove groups.json
|
||||
ansible.builtin.file:
|
||||
state: absent
|
||||
path: groups.json
|
||||
47
tests/group/test_groups_present_slice.yml
Normal file
47
tests/group/test_groups_present_slice.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
- name: Include create_groups_json.yml
|
||||
ansible.builtin.import_playbook: create_groups_json.yml
|
||||
|
||||
- name: Test groups present slice
|
||||
hosts: ipaserver
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
slice_size: 500
|
||||
tasks:
|
||||
- name: Include groups.json
|
||||
ansible.builtin.include_vars:
|
||||
file: groups.json # noqa 505
|
||||
|
||||
- name: Size of groups slice.
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ group_list | length }}"
|
||||
|
||||
- name: Groups present
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups: "{{ group_list[item : item + slice_size] }}"
|
||||
loop: "{{ range(0, group_list | length, slice_size) | list }}"
|
||||
|
||||
- name: Initialize groups_names
|
||||
ansible.builtin.set_fact:
|
||||
groups_names: []
|
||||
|
||||
- name: Create dict with group names
|
||||
ansible.builtin.set_fact:
|
||||
groups_names: "{{ groups_names | default([]) + [{'name': item.name}] }}"
|
||||
loop: "{{ group_list }}"
|
||||
|
||||
- name: Remove groups
|
||||
ipagroup:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
groups: "{{ groups_names }}"
|
||||
state: absent
|
||||
|
||||
- name: Remove groups.json
|
||||
hosts: localhost
|
||||
tasks:
|
||||
- name: Remove groups.json
|
||||
ansible.builtin.file:
|
||||
state: absent
|
||||
path: groups.json
|
||||
@@ -49,6 +49,26 @@
|
||||
- "{{ host1_fqdn }}"
|
||||
state: absent
|
||||
|
||||
- name: Host "{{ host1_fqdn }}" is present with random password using hosts parameter
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
hosts:
|
||||
- name: "{{ host1_fqdn }}"
|
||||
random: yes
|
||||
force: yes
|
||||
update_password: on_create
|
||||
register: ipahost
|
||||
failed_when: not ipahost.changed or
|
||||
ipahost.host[host1_fqdn].randompassword is not defined or
|
||||
ipahost.failed
|
||||
|
||||
- name: Host "{{ host1_fqdn }}" absent
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name:
|
||||
- "{{ host1_fqdn }}"
|
||||
state: absent
|
||||
|
||||
- name: Hosts "{{ host1_fqdn }}" and "{{ host2_fqdn }}" present with random password
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
ansible.builtin.include_tasks: ../env_freeipa_facts.yml
|
||||
|
||||
- name: Tests requiring IPA version 4.8.4+
|
||||
when: ipa_version is version('4.8.4', '>=')
|
||||
block:
|
||||
- name: Ensure host-group testhostgroup is absent
|
||||
ipahostgroup:
|
||||
@@ -224,4 +225,3 @@
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
when: ipa_version is version('4.8.4', '>=')
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
ansible.builtin.include_tasks: ../env_freeipa_facts.yml
|
||||
|
||||
- name: Tests requiring IPA version 4.8.7+
|
||||
when: ipa_version is version('4.8.7', '>=')
|
||||
block:
|
||||
- name: Ensure testing host-group are absent
|
||||
ipahostgroup:
|
||||
@@ -108,5 +109,3 @@
|
||||
- databases
|
||||
- datalake
|
||||
state: absent
|
||||
|
||||
when: ipa_version is version('4.8.7', '>=')
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
name: local_id_range
|
||||
|
||||
- name: Execute idrange tests if trust test environment is supported
|
||||
when: trust_test_is_supported | default(false)
|
||||
block:
|
||||
# Create trust with range_type: ipa-ad-trust
|
||||
- name: Create trust with range_type 'ipa-ad-trust'
|
||||
@@ -367,5 +368,3 @@
|
||||
- ad_posix_id_range
|
||||
continue: yes
|
||||
state: absent
|
||||
|
||||
when: trust_test_is_supported | default(false)
|
||||
|
||||
@@ -223,7 +223,7 @@
|
||||
ipapwpolicy:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
maxrepeat: 4
|
||||
maxsequence: 4
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
@@ -231,7 +231,7 @@
|
||||
ipapwpolicy:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
maxrepeat: 4
|
||||
maxsequence: 4
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
@@ -239,7 +239,7 @@
|
||||
ipapwpolicy:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
maxrepeat: 0
|
||||
maxsequence: 0
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ def pytest_configure(config):
|
||||
if os.path.exists(config_dir):
|
||||
inventory_path = os.path.join(config_dir, "test.inventory.yml")
|
||||
inventory = get_inventory(inventory_path)
|
||||
print("Configuring execution using {}".format(inventory_path))
|
||||
print("Configuring execution using {0}".format(inventory_path))
|
||||
ipaservers = inventory["all"]["children"]["ipaserver"]["hosts"]
|
||||
ipaserver = list(ipaservers.values())[0]
|
||||
private_key = os.path.join(config_dir, "id_rsa")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
- name: Ensure ipaserver_domain is set
|
||||
when: ipaserver_domain is not defined
|
||||
block:
|
||||
- name: Get Domain from server name
|
||||
ansible.builtin.set_fact:
|
||||
@@ -9,7 +10,6 @@
|
||||
ansible.builtin.set_fact:
|
||||
ipaserver_domain: "ipa.test"
|
||||
when: "'fqdn' not in ansible_facts"
|
||||
when: ipaserver_domain is not defined
|
||||
|
||||
- name: Set ipaserver_realm.
|
||||
ansible.builtin.set_fact:
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
plugins/module_utils/ansible_freeipa_module.py compile-2.6!skip
|
||||
plugins/module_utils/ansible_freeipa_module.py import-2.6!skip
|
||||
plugins/module_utils/ansible_freeipa_module.py pylint:ansible-bad-function
|
||||
plugins/modules/ipaclient_get_facts.py compile-2.6!skip
|
||||
plugins/modules/ipaclient_get_facts.py import-2.6!skip
|
||||
plugins/modules/ipaclient_api.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipaclient_join.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipaclient_test.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipaconfig.py compile-2.6!skip
|
||||
plugins/modules/ipaconfig.py import-2.6!skip
|
||||
plugins/modules/ipadnsrecord.py compile-2.6!skip
|
||||
plugins/modules/ipadnsrecord.py import-2.6!skip
|
||||
plugins/modules/ipadnsrecord.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipadnsrecord.py pylint:use-maxsplit-arg
|
||||
plugins/modules/ipareplica_enable_ipa.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipareplica_prepare.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipareplica_test.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/iparole.py compile-2.6!skip
|
||||
plugins/modules/iparole.py import-2.6!skip
|
||||
plugins/modules/ipaserver_setup_ca.py compile-2.6!skip
|
||||
plugins/modules/ipaserver_setup_ca.py import-2.6!skip
|
||||
plugins/modules/ipaserver_test.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipaservice.py compile-2.6!skip
|
||||
plugins/modules/ipaservice.py import-2.6!skip
|
||||
plugins/modules/ipasudorule.py compile-2.6!skip
|
||||
plugins/modules/ipasudorule.py import-2.6!skip
|
||||
plugins/modules/ipavault.py compile-2.6!skip
|
||||
plugins/modules/ipavault.py import-2.6!skip
|
||||
roles/ipaclient/library/ipaclient_api.py pylint:ansible-format-automatic-specification
|
||||
roles/ipaclient/library/ipaclient_join.py pylint:ansible-format-automatic-specification
|
||||
roles/ipaclient/library/ipaclient_test.py pylint:ansible-format-automatic-specification
|
||||
roles/ipareplica/library/ipareplica_enable_ipa.py pylint:ansible-format-automatic-specification
|
||||
roles/ipareplica/library/ipareplica_prepare.py pylint:ansible-format-automatic-specification
|
||||
roles/ipareplica/library/ipareplica_test.py pylint:ansible-format-automatic-specification
|
||||
roles/ipaserver/library/ipaserver_test.py pylint:ansible-format-automatic-specification
|
||||
roles/ipareplica/module_utils/ansible_ipa_replica.py pylint:ansible-format-automatic-specification
|
||||
tests/external-signed-ca-with-automatic-copy/external-ca.sh shebang!skip
|
||||
tests/pytests/conftest.py pylint:ansible-format-automatic-specification
|
||||
tests/sanity/sanity.sh shebang!skip
|
||||
tests/user/users.sh shebang!skip
|
||||
tests/user/users_absent.sh shebang!skip
|
||||
tests/utils.py pylint:ansible-format-automatic-specification
|
||||
utils/ansible-doc-test shebang!skip
|
||||
utils/build-galaxy-release.sh shebang!skip
|
||||
utils/build-srpm.sh shebang!skip
|
||||
utils/changelog shebang!skip
|
||||
utils/check_test_configuration.py shebang!skip
|
||||
utils/galaxyfy-README.py shebang!skip
|
||||
utils/galaxyfy-module-EXAMPLES.py shebang!skip
|
||||
utils/galaxyfy-playbook.py shebang!skip
|
||||
utils/galaxyfy.py shebang!skip
|
||||
utils/gen_modules_docs.sh shebang!skip
|
||||
utils/lint_check.sh shebang!skip
|
||||
utils/new_module shebang!skip
|
||||
@@ -1,36 +0,0 @@
|
||||
plugins/module_utils/ansible_freeipa_module.py pylint:ansible-bad-function
|
||||
plugins/modules/ipaclient_api.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipaclient_join.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipaclient_test.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipadnsrecord.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipadnsrecord.py pylint:use-maxsplit-arg
|
||||
plugins/modules/ipareplica_enable_ipa.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipareplica_prepare.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipareplica_test.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipaserver_test.py pylint:ansible-format-automatic-specification
|
||||
roles/ipaclient/library/ipaclient_api.py pylint:ansible-format-automatic-specification
|
||||
roles/ipaclient/library/ipaclient_join.py pylint:ansible-format-automatic-specification
|
||||
roles/ipaclient/library/ipaclient_test.py pylint:ansible-format-automatic-specification
|
||||
roles/ipareplica/library/ipareplica_enable_ipa.py pylint:ansible-format-automatic-specification
|
||||
roles/ipareplica/library/ipareplica_prepare.py pylint:ansible-format-automatic-specification
|
||||
roles/ipareplica/library/ipareplica_test.py pylint:ansible-format-automatic-specification
|
||||
roles/ipaserver/library/ipaserver_test.py pylint:ansible-format-automatic-specification
|
||||
roles/ipareplica/module_utils/ansible_ipa_replica.py pylint:ansible-format-automatic-specification
|
||||
tests/external-signed-ca-with-automatic-copy/external-ca.sh shebang!skip
|
||||
tests/pytests/conftest.py pylint:ansible-format-automatic-specification
|
||||
tests/sanity/sanity.sh shebang!skip
|
||||
tests/user/users.sh shebang!skip
|
||||
tests/user/users_absent.sh shebang!skip
|
||||
tests/utils.py pylint:ansible-format-automatic-specification
|
||||
utils/ansible-doc-test shebang!skip
|
||||
utils/build-galaxy-release.sh shebang!skip
|
||||
utils/build-srpm.sh shebang!skip
|
||||
utils/changelog shebang!skip
|
||||
utils/check_test_configuration.py shebang!skip
|
||||
utils/galaxyfy-README.py shebang!skip
|
||||
utils/galaxyfy-module-EXAMPLES.py shebang!skip
|
||||
utils/galaxyfy-playbook.py shebang!skip
|
||||
utils/galaxyfy.py shebang!skip
|
||||
utils/gen_modules_docs.sh shebang!skip
|
||||
utils/lint_check.sh shebang!skip
|
||||
utils/new_module shebang!skip
|
||||
@@ -1,36 +0,0 @@
|
||||
plugins/module_utils/ansible_freeipa_module.py pylint:ansible-bad-function
|
||||
plugins/modules/ipaclient_api.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipaclient_join.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipaclient_test.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipadnsrecord.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipadnsrecord.py pylint:use-maxsplit-arg
|
||||
plugins/modules/ipareplica_enable_ipa.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipareplica_prepare.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipareplica_test.py pylint:ansible-format-automatic-specification
|
||||
plugins/modules/ipaserver_test.py pylint:ansible-format-automatic-specification
|
||||
roles/ipaclient/library/ipaclient_api.py pylint:ansible-format-automatic-specification
|
||||
roles/ipaclient/library/ipaclient_join.py pylint:ansible-format-automatic-specification
|
||||
roles/ipaclient/library/ipaclient_test.py pylint:ansible-format-automatic-specification
|
||||
roles/ipareplica/library/ipareplica_enable_ipa.py pylint:ansible-format-automatic-specification
|
||||
roles/ipareplica/library/ipareplica_prepare.py pylint:ansible-format-automatic-specification
|
||||
roles/ipareplica/library/ipareplica_test.py pylint:ansible-format-automatic-specification
|
||||
roles/ipaserver/library/ipaserver_test.py pylint:ansible-format-automatic-specification
|
||||
roles/ipareplica/module_utils/ansible_ipa_replica.py pylint:ansible-format-automatic-specification
|
||||
tests/external-signed-ca-with-automatic-copy/external-ca.sh shebang!skip
|
||||
tests/pytests/conftest.py pylint:ansible-format-automatic-specification
|
||||
tests/sanity/sanity.sh shebang!skip
|
||||
tests/user/users.sh shebang!skip
|
||||
tests/user/users_absent.sh shebang!skip
|
||||
tests/utils.py pylint:ansible-format-automatic-specification
|
||||
utils/ansible-doc-test shebang!skip
|
||||
utils/build-galaxy-release.sh shebang!skip
|
||||
utils/build-srpm.sh shebang!skip
|
||||
utils/changelog shebang!skip
|
||||
utils/check_test_configuration.py shebang!skip
|
||||
utils/galaxyfy-README.py shebang!skip
|
||||
utils/galaxyfy-module-EXAMPLES.py shebang!skip
|
||||
utils/galaxyfy-playbook.py shebang!skip
|
||||
utils/galaxyfy.py shebang!skip
|
||||
utils/gen_modules_docs.sh shebang!skip
|
||||
utils/lint_check.sh shebang!skip
|
||||
utils/new_module shebang!skip
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/bash -eu
|
||||
|
||||
TOPDIR=$(readlink -f "$(dirname "$0")/../..")
|
||||
pushd "${TOPDIR}" >/dev/null || exit 1
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
# CLEANUP TEST ITEMS
|
||||
- name: Ensure ipa_server_name is set
|
||||
when: ipa_server_name is not defined
|
||||
block:
|
||||
- name: Get server name from hostname
|
||||
ansible.builtin.set_fact:
|
||||
@@ -16,9 +17,9 @@
|
||||
- name: Fallback to 'ipaserver'
|
||||
ansible.builtin.set_fact:
|
||||
ipa_server_name: ipaserver
|
||||
when: ipa_server_name is not defined
|
||||
|
||||
- name: Ensure ipaserver_domain is set
|
||||
when: ipaserver_domain is not defined
|
||||
block:
|
||||
- name: Get domain name from hostname.
|
||||
ansible.builtin.set_fact:
|
||||
@@ -27,7 +28,6 @@
|
||||
- name: Fallback to 'ipa.test'
|
||||
ansible.builtin.set_fact:
|
||||
ipaserver_domain: "ipa.test"
|
||||
when: ipaserver_domain is not defined
|
||||
|
||||
- name: Ensure server "{{ ipa_server_name + '.' + ipaserver_domain }}" without location
|
||||
ipaserver:
|
||||
|
||||
200
tests/service/certificate/test_service_certificate_newline.yml
Normal file
200
tests/service/certificate/test_service_certificate_newline.yml
Normal file
@@ -0,0 +1,200 @@
|
||||
---
|
||||
- name: Test service with certificates with and without trailing new line
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Include tasks ../../env_freeipa_facts.yml
|
||||
ansible.builtin.include_tasks: ../../env_freeipa_facts.yml
|
||||
|
||||
- name: Setup test environment
|
||||
ansible.builtin.include_tasks: ../env_vars.yml
|
||||
|
||||
- name: Generate self-signed certificates.
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
openssl req -x509 -newkey rsa:2048 -days 365 -nodes -keyout "private{{ item }}.key" -out "cert{{ item }}.pem" -subj '/CN=test'
|
||||
openssl x509 -outform der -in "cert{{ item }}.pem" -out "cert{{ item }}.der"
|
||||
base64 "cert{{ item }}.der" -w5000 > "cert{{ item }}.b64"
|
||||
with_items: [1, 2, 3]
|
||||
become: no
|
||||
delegate_to: localhost
|
||||
|
||||
# The rstrip=False for lookup will add keep the newline at the end of the
|
||||
# cert and this is automatically revoved in IPA, This is an additional
|
||||
# test of ipaservice later on to behave correctly in both cases.
|
||||
- name: Set fact cert1,2,3 from lookup
|
||||
ansible.builtin.set_fact:
|
||||
cert1: "{{ lookup('file', 'cert1.b64', rstrip=False) }}"
|
||||
cert2: "{{ lookup('file', 'cert2.b64', rstrip=True) }}"
|
||||
cert3: "{{ lookup('file', 'cert3.b64', rstrip=False) }}"
|
||||
|
||||
- name: Host {{ svc_fqdn }} absent
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: "{{ svc_fqdn }}"
|
||||
state: absent
|
||||
|
||||
- name: Host {{ svc_fqdn }} present
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: "{{ svc_fqdn }}"
|
||||
force: true
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Service FOO/{{ svc_fqdn }} absent
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: "FOO/{{ svc_fqdn }}"
|
||||
continue: true
|
||||
state: absent
|
||||
|
||||
- name: Service FOO/{{ svc_fqdn }} present
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: "FOO/{{ svc_fqdn }}"
|
||||
force: yes
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Service FOO/{{ svc_fqdn }} certs 1,2 members present
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert1 }}"
|
||||
- "{{ cert2 }}"
|
||||
action: member
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Service FOO/{{ svc_fqdn }} certs 1,2 members present again
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert1 }}"
|
||||
- "{{ cert2 }}"
|
||||
action: member
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Service FOO/{{ svc_fqdn }} certs 1,2,3 members present
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert1 }}"
|
||||
- "{{ cert2 }}"
|
||||
- "{{ cert3 }}"
|
||||
action: member
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Service FOO/{{ svc_fqdn }} certs 1,2,3 members present again
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert1 }}"
|
||||
- "{{ cert2 }}"
|
||||
- "{{ cert3 }}"
|
||||
action: member
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Service FOO/{{ svc_fqdn }} certs 2,3 member absent
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert2 }}"
|
||||
- "{{ cert3 }}"
|
||||
state: absent
|
||||
action: member
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Service FOO/{{ svc_fqdn }} certs 2,3 member absent again
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert2 }}"
|
||||
- "{{ cert3 }}"
|
||||
action: member
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Service FOO/{{ svc_fqdn }} certs 1,2,3 members absent
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert1 }}"
|
||||
- "{{ cert2 }}"
|
||||
- "{{ cert3 }}"
|
||||
action: member
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Service FOO/{{ svc_fqdn }} certs 1,2,3 members absent again
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert1 }}"
|
||||
- "{{ cert2 }}"
|
||||
- "{{ cert3 }}"
|
||||
action: member
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Service FOO/{{ svc_fqdn }} absent
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: "FOO/{{ svc_fqdn }}"
|
||||
continue: true
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Service FOO/{{ svc_fqdn }} absent again
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: "FOO/{{ svc_fqdn }}"
|
||||
continue: true
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Host {{ svc_fqdn }} absent
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: "{{ svc_fqdn }}"
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Remove certificate files. # noqa: deprecated-command-syntax
|
||||
ansible.builtin.shell:
|
||||
cmd: rm -f "private{{ item }}.key" "cert{{ item }}.pem" "cert{{ item }}.der" "cert{{ item }}.b64"
|
||||
with_items: [1, 2, 3]
|
||||
become: no
|
||||
delegate_to: localhost
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user