mirror of
https://github.com/freeipa/ansible-freeipa.git
synced 2026-03-30 15:23:06 +00:00
Compare commits
45 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 |
@@ -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
|
||||
@@ -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)
|
||||
|
||||
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
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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'.")
|
||||
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
|
||||
@@ -236,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]))
|
||||
|
||||
@@ -241,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")
|
||||
@@ -254,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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -211,6 +211,7 @@ options:
|
||||
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
|
||||
@@ -1054,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:
|
||||
@@ -1067,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
|
||||
|
||||
@@ -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"
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/bash -eu
|
||||
|
||||
master=$1
|
||||
if [ -z "$master" ]; then
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/bash -eu
|
||||
|
||||
NUM=${1-1000}
|
||||
FILE="groups.json"
|
||||
|
||||
@@ -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,55 +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/group/groups.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,37 +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/group/groups.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,37 +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/group/groups.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
|
||||
|
||||
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
|
||||
314
tests/service/certificate/test_services_certificate_newline.yml
Normal file
314
tests/service/certificate/test_services_certificate_newline.yml
Normal file
@@ -0,0 +1,314 @@
|
||||
---
|
||||
- name: Test services 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: [11, 12, 13, 21, 22, 23, 31, 32, 33]
|
||||
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 for certs 11,12,13,21,22,23,31,32,33 from lookup
|
||||
ansible.builtin.set_fact:
|
||||
cert11: "{{ lookup('file', 'cert11.b64', rstrip=True) }}"
|
||||
cert12: "{{ lookup('file', 'cert12.b64', rstrip=False) }}"
|
||||
cert13: "{{ lookup('file', 'cert13.b64', rstrip=True) }}"
|
||||
cert21: "{{ lookup('file', 'cert21.b64', rstrip=False) }}"
|
||||
cert22: "{{ lookup('file', 'cert22.b64', rstrip=False) }}"
|
||||
cert23: "{{ lookup('file', 'cert23.b64', rstrip=True) }}"
|
||||
cert31: "{{ lookup('file', 'cert31.b64', rstrip=False) }}"
|
||||
cert32: "{{ lookup('file', 'cert32.b64', rstrip=True) }}"
|
||||
cert33: "{{ lookup('file', 'cert33.b64', rstrip=False) }}"
|
||||
|
||||
- name: Services FOO,BAR,BAZ/{{ svc_fqdn }} absent
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name:
|
||||
- "FOO/{{ svc_fqdn }}"
|
||||
- "BAR/{{ svc_fqdn }}"
|
||||
- "BAZ/{{ svc_fqdn }}"
|
||||
continue: true
|
||||
state: absent
|
||||
|
||||
- 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: Services FOO,BAR,BAZ/{{ svc_fqdn }} present
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
services:
|
||||
- name: "FOO/{{ svc_fqdn }}"
|
||||
force: yes
|
||||
- name: "BAR/{{ svc_fqdn }}"
|
||||
force: yes
|
||||
- name: "BAZ/{{ svc_fqdn }}"
|
||||
force: yes
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Services FOO,BAR,BAZ/{{ svc_fqdn }} present
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
services:
|
||||
- name: "FOO/{{ svc_fqdn }}"
|
||||
force: yes
|
||||
- name: "BAR/{{ svc_fqdn }}"
|
||||
force: yes
|
||||
- name: "BAZ/{{ svc_fqdn }}"
|
||||
force: yes
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Service FOO,BAR,BAZ/{{ svc_fqdn }} certs x1,x2 members present
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
services:
|
||||
- name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert11 }}"
|
||||
- "{{ cert12 }}"
|
||||
- name: "BAR/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert21 }}"
|
||||
- "{{ cert22 }}"
|
||||
- name: "BAZ/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert31 }}"
|
||||
- "{{ cert32 }}"
|
||||
action: member
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Service FOO,BAR,BAZ/{{ svc_fqdn }} certs x1,x2 members present again
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
services:
|
||||
- name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert11 }}"
|
||||
- "{{ cert12 }}"
|
||||
- name: "BAR/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert21 }}"
|
||||
- "{{ cert22 }}"
|
||||
- name: "BAZ/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert31 }}"
|
||||
- "{{ cert32 }}"
|
||||
action: member
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Service FOO,BAR,BAZ/{{ svc_fqdn }} certs x1,x2,x3 members present
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
services:
|
||||
- name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert11 }}"
|
||||
- "{{ cert12 }}"
|
||||
- "{{ cert13 }}"
|
||||
- name: "BAR/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert21 }}"
|
||||
- "{{ cert22 }}"
|
||||
- "{{ cert23 }}"
|
||||
- name: "BAZ/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert31 }}"
|
||||
- "{{ cert32 }}"
|
||||
- "{{ cert33 }}"
|
||||
action: member
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Service FOO,BAR,BAZ/{{ svc_fqdn }} certs x1,x2,x3 members present again
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
services:
|
||||
- name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert11 }}"
|
||||
- "{{ cert12 }}"
|
||||
- "{{ cert13 }}"
|
||||
- name: "BAR/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert21 }}"
|
||||
- "{{ cert22 }}"
|
||||
- "{{ cert23 }}"
|
||||
- name: "BAZ/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert31 }}"
|
||||
- "{{ cert32 }}"
|
||||
- "{{ cert33 }}"
|
||||
action: member
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Service FOO,BAR,BAZ/{{ svc_fqdn }} certs x2,x3 members absent
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
services:
|
||||
- name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert12 }}"
|
||||
- "{{ cert13 }}"
|
||||
- name: "BAR/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert22 }}"
|
||||
- "{{ cert23 }}"
|
||||
- name: "BAZ/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert32 }}"
|
||||
- "{{ cert33 }}"
|
||||
action: member
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Service FOO,BAR,BAZ/{{ svc_fqdn }} certs x2,x3 members absent, again
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
services:
|
||||
- name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert12 }}"
|
||||
- "{{ cert13 }}"
|
||||
- name: "BAR/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert22 }}"
|
||||
- "{{ cert23 }}"
|
||||
- name: "BAZ/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert32 }}"
|
||||
- "{{ cert33 }}"
|
||||
action: member
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Service FOO,BAR,BAZ/{{ svc_fqdn }} certs x1,x2,x3 members absent
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
services:
|
||||
- name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert11 }}"
|
||||
- "{{ cert12 }}"
|
||||
- "{{ cert13 }}"
|
||||
- name: "BAR/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert21 }}"
|
||||
- "{{ cert22 }}"
|
||||
- "{{ cert23 }}"
|
||||
- name: "BAZ/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert31 }}"
|
||||
- "{{ cert32 }}"
|
||||
- "{{ cert33 }}"
|
||||
action: member
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Service FOO,BAR,BAZ/{{ svc_fqdn }} certs x1,x2,x3 members absent, again
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
services:
|
||||
- name: "FOO/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert11 }}"
|
||||
- "{{ cert12 }}"
|
||||
- "{{ cert13 }}"
|
||||
- name: "BAR/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert21 }}"
|
||||
- "{{ cert22 }}"
|
||||
- "{{ cert23 }}"
|
||||
- name: "BAZ/{{ svc_fqdn }}"
|
||||
certificate:
|
||||
- "{{ cert31 }}"
|
||||
- "{{ cert32 }}"
|
||||
- "{{ cert33 }}"
|
||||
action: member
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Services FOO,BAR,BAZ/{{ svc_fqdn }} absent
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name:
|
||||
- "FOO/{{ svc_fqdn }}"
|
||||
- "BAR/{{ svc_fqdn }}"
|
||||
- "BAZ/{{ svc_fqdn }}"
|
||||
continue: true
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Services FOO,BAR,BAZ/{{ svc_fqdn }} absent, again
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name:
|
||||
- "FOO/{{ svc_fqdn }}"
|
||||
- "BAR/{{ svc_fqdn }}"
|
||||
- "BAZ/{{ 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: [11, 12, 13, 21, 22, 23, 31, 32, 33]
|
||||
become: no
|
||||
delegate_to: localhost
|
||||
98
tests/service/generate_test_data.yml
Normal file
98
tests/service/generate_test_data.yml
Normal file
@@ -0,0 +1,98 @@
|
||||
# Generate lists for hosts and services
|
||||
---
|
||||
- name: Get Domain from server name
|
||||
ansible.builtin.set_fact:
|
||||
ipaserver_domain: "{{ ansible_facts['fqdn'].split('.')[1:] | join('.') }}"
|
||||
when: ipaserver_domain is not defined
|
||||
|
||||
- name: Create present services.json data
|
||||
ansible.builtin.shell: |
|
||||
echo "["
|
||||
for i in $(seq 1 "{{ NUM }}"); do
|
||||
echo " {"
|
||||
echo " \"name\": \"HTTP/www$i.{{ DOMAIN }}\","
|
||||
echo " \"principal\": \"host/test$i.{{ DOMAIN }}\","
|
||||
echo " \"force\": \"true\""
|
||||
if [ "$i" -lt "{{ NUM }}" ]; then
|
||||
echo " },"
|
||||
else
|
||||
echo " }"
|
||||
fi
|
||||
done
|
||||
echo "]"
|
||||
vars:
|
||||
NUM: 500
|
||||
DOMAIN: "{{ ipaserver_domain }}"
|
||||
register: command
|
||||
|
||||
- name: Set service_list
|
||||
ansible.builtin.set_fact:
|
||||
service_list: "{{ command.stdout | from_json }}"
|
||||
|
||||
- name: Create absent services.json data
|
||||
ansible.builtin.shell: |
|
||||
echo "["
|
||||
for i in $(seq 1 "{{ NUM }}"); do
|
||||
echo " {"
|
||||
echo " \"name\": \"HTTP/www$i.{{ DOMAIN }}\","
|
||||
echo " \"continue\": \"true\""
|
||||
if [ "$i" -lt "{{ NUM }}" ]; then
|
||||
echo " },"
|
||||
else
|
||||
echo " }"
|
||||
fi
|
||||
done
|
||||
echo "]"
|
||||
vars:
|
||||
NUM: 500
|
||||
DOMAIN: "{{ ipaserver_domain }}"
|
||||
register: command
|
||||
|
||||
- name: Set service_absent_list
|
||||
ansible.builtin.set_fact:
|
||||
service_absent_list: "{{ command.stdout | from_json }}"
|
||||
|
||||
- name: Create present hosts.json data
|
||||
ansible.builtin.shell: |
|
||||
echo "["
|
||||
for i in $(seq 1 "{{ NUM }}"); do
|
||||
echo " {"
|
||||
echo " \"name\": \"www$i.{{ DOMAIN }}\","
|
||||
echo " \"force\": \"true\""
|
||||
if [ "$i" -lt "{{ NUM }}" ]; then
|
||||
echo " },"
|
||||
else
|
||||
echo " }"
|
||||
fi
|
||||
done
|
||||
echo "]"
|
||||
vars:
|
||||
NUM: 500
|
||||
DOMAIN: "{{ ipaserver_domain }}"
|
||||
register: command
|
||||
|
||||
- name: Set host_list
|
||||
ansible.builtin.set_fact:
|
||||
host_list: "{{ command.stdout | from_json }}"
|
||||
|
||||
- name: Create absent hosts.json data
|
||||
ansible.builtin.shell: |
|
||||
echo "["
|
||||
for i in $(seq 1 "{{ NUM }}"); do
|
||||
echo " {"
|
||||
echo " \"name\": \"www$i.{{ DOMAIN }}\""
|
||||
if [ "$i" -lt "{{ NUM }}" ]; then
|
||||
echo " },"
|
||||
else
|
||||
echo " }"
|
||||
fi
|
||||
done
|
||||
echo "]"
|
||||
vars:
|
||||
NUM: 500
|
||||
DOMAIN: "{{ ipaserver_domain }}"
|
||||
register: command
|
||||
|
||||
- name: Set host_absent_list
|
||||
ansible.builtin.set_fact:
|
||||
host_absent_list: "{{ command.stdout | from_json }}"
|
||||
15
tests/service/test_services_absent.yml
Normal file
15
tests/service/test_services_absent.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Test services absent
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: Include generate_test_data.yml
|
||||
ansible.builtin.include_tasks: generate_test_data.yml
|
||||
|
||||
- name: Services absent len:{{ service_list | length }}
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services: "{{ service_absent_list }}"
|
||||
state: absent
|
||||
71
tests/service/test_services_present.yml
Normal file
71
tests/service/test_services_present.yml
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
- name: Test services present
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: true
|
||||
|
||||
tasks:
|
||||
- name: Include generate_test_data.yml
|
||||
ansible.builtin.include_tasks: generate_test_data.yml
|
||||
|
||||
- name: Hosts present len:{{ host_list | length }}
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
hosts: "{{ host_list }}"
|
||||
force: true
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Hosts present len:{{ host_list | length }}, again
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
hosts: "{{ host_list }}"
|
||||
force: true
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Services present len:{{ service_list | length }}
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services: "{{ service_list }}"
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Services present len:{{ service_list | length }}, again
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services: "{{ service_list }}"
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Services absent len:{{ service_list | length }}
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services: "{{ service_absent_list }}"
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Services absent len:{{ service_list | length }}, again
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services: "{{ service_absent_list }}"
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Hosts absent len:{{ host_list | length }}
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
hosts: "{{ host_absent_list }}"
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Hosts absent len:{{ host_list | length }}, again
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
hosts: "{{ host_absent_list }}"
|
||||
state: absent
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
91
tests/service/test_services_present_slice.yml
Normal file
91
tests/service/test_services_present_slice.yml
Normal file
@@ -0,0 +1,91 @@
|
||||
---
|
||||
- name: Test services present slice
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: true
|
||||
|
||||
vars:
|
||||
slice_size: 100
|
||||
tasks:
|
||||
- name: Include generate_test_data.yml
|
||||
ansible.builtin.include_tasks: generate_test_data.yml
|
||||
|
||||
- name: Size of slice
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ slice_size }}"
|
||||
|
||||
- name: Size of services list
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ service_list | length }}"
|
||||
|
||||
- name: Size of hosts list
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ host_list | length }}"
|
||||
|
||||
- name: Hosts present
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
hosts: "{{ host_list[item : item + slice_size] }}"
|
||||
loop: "{{ range(0, host_list | length, slice_size) | list }}"
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Hosts present, again
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
hosts: "{{ host_list[item : item + slice_size] }}"
|
||||
loop: "{{ range(0, host_list | length, slice_size) | list }}"
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Services present
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services: "{{ service_list[item : item + slice_size] }}"
|
||||
loop: "{{ range(0, service_list | length, slice_size) | list }}"
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Services present, again
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services: "{{ service_list[item : item + slice_size] }}"
|
||||
loop: "{{ range(0, service_list | length, slice_size) | list }}"
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Services absent
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services: "{{ service_absent_list[item : item + slice_size] }}"
|
||||
state: absent
|
||||
loop: "{{ range(0, service_absent_list | length, slice_size) | list }}"
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Services absent, again
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services: "{{ service_absent_list[item : item + slice_size] }}"
|
||||
state: absent
|
||||
loop: "{{ range(0, service_absent_list | length, slice_size) | list }}"
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Hosts absent
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
hosts: "{{ host_absent_list[item : item + slice_size] }}"
|
||||
state: absent
|
||||
loop: "{{ range(0, host_absent_list | length, slice_size) | list }}"
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Hosts absent, again
|
||||
ipahost:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
hosts: "{{ host_absent_list[item : item + slice_size] }}"
|
||||
state: absent
|
||||
loop: "{{ range(0, host_absent_list | length, slice_size) | list }}"
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
100
tests/service/test_services_without_skip_host_check.yml
Normal file
100
tests/service/test_services_without_skip_host_check.yml
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
- name: Test services without using option skip_host_check
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
# setup
|
||||
- name: Test services without using option skip_host_check
|
||||
block:
|
||||
- name: Setup test environment
|
||||
ansible.builtin.include_tasks: env_setup.yml
|
||||
|
||||
- name: Services are present
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services:
|
||||
- name: "HTTP/{{ svc_fqdn }}"
|
||||
principal:
|
||||
- host/test.example.com
|
||||
- name: "mysvc/{{ host1_fqdn }}"
|
||||
pac_type: NONE
|
||||
ok_as_delegate: yes
|
||||
ok_to_auth_as_delegate: yes
|
||||
- name: "HTTP/{{ host1_fqdn }}"
|
||||
allow_create_keytab_user:
|
||||
- user01
|
||||
- user02
|
||||
allow_create_keytab_group:
|
||||
- group01
|
||||
- group02
|
||||
allow_create_keytab_host:
|
||||
- "{{ host1_fqdn }}"
|
||||
- "{{ host2_fqdn }}"
|
||||
allow_create_keytab_hostgroup:
|
||||
- hostgroup01
|
||||
- hostgroup02
|
||||
- name: "mysvc/{{ host2_fqdn }}"
|
||||
auth_ind: otp,radius
|
||||
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Services are present again
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services:
|
||||
- name: "HTTP/{{ svc_fqdn }}"
|
||||
- name: "mysvc/{{ host1_fqdn }}"
|
||||
- name: "HTTP/{{ host1_fqdn }}"
|
||||
- name: "mysvc/{{ host2_fqdn }}"
|
||||
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 services with the same name should be added in the same
|
||||
# task.
|
||||
- name: Duplicate names in services failure test
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
services:
|
||||
- name: "HTTP/{{ svc_fqdn }}"
|
||||
- name: "mysvc/{{ host1_fqdn }}"
|
||||
- name: "HTTP/{{ nohost_fqdn }}"
|
||||
- name: "HTTP/{{ svc_fqdn }}"
|
||||
register: result
|
||||
failed_when: result.changed or not result.failed or "is used more than once" not in result.msg
|
||||
|
||||
- name: Services/name and name 'service' present
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: "HTTP/{{ svc_fqdn }}"
|
||||
services:
|
||||
- name: "HTTP/{{ svc_fqdn }}"
|
||||
register: result
|
||||
failed_when: result.changed or not result.failed or "parameters are mutually exclusive" not in result.msg
|
||||
|
||||
- name: Services/name and name are absent
|
||||
ipaservice:
|
||||
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
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name:
|
||||
register: result
|
||||
failed_when: result.changed or not result.failed or "At least one name or services is required" not in result.msg
|
||||
|
||||
- name: Only one service can be added at a time using name.
|
||||
ipaservice:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: example.com,example1.com
|
||||
register: result
|
||||
failed_when: result.changed or not result.failed or "Only one service can be added at a time using 'name'." not in result.msg
|
||||
|
||||
always:
|
||||
# cleanup
|
||||
- name: Cleanup test environment
|
||||
ansible.builtin.include_tasks: env_cleanup.yml
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/bash -eu
|
||||
|
||||
NUM=${1-1000}
|
||||
FILE="users.json"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/bash -eu
|
||||
|
||||
NUM=1000
|
||||
FILE="users_absent.json"
|
||||
|
||||
@@ -180,7 +180,7 @@ def run_playbook(playbook, allow_failures=False):
|
||||
if allow_failures:
|
||||
return result
|
||||
|
||||
status_code_msg = "ansible-playbook return code: {}".format(
|
||||
status_code_msg = "ansible-playbook return code: {0}".format(
|
||||
result.returncode
|
||||
)
|
||||
assert_msg = "\n".join(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
|
||||
@@ -125,7 +125,7 @@ done
|
||||
|
||||
for i in utils/*.py utils/new_module utils/changelog utils/ansible-doc-test;
|
||||
do
|
||||
sed -i '{s@/usr/bin/python*@%{python}@}' $i
|
||||
sed -i '{s@/usr/bin/env python*@%{python}@}' $i
|
||||
done
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/bash -eu
|
||||
#
|
||||
# Build Ansible Collection from ansible-freeipa repo
|
||||
#
|
||||
@@ -114,6 +114,8 @@ echo -e "\033[ACreating CHANGELOG.rst... \033[32;1mDONE\033[0m"
|
||||
|
||||
sed -i -e "s/ansible.module_utils.ansible_freeipa_module/ansible_collections.${collection_prefix}.plugins.module_utils.ansible_freeipa_module/" plugins/modules/*.py
|
||||
|
||||
python utils/create_action_group.py "meta/runtime.yml" "$collection_prefix"
|
||||
|
||||
(cd plugins/module_utils && {
|
||||
ln -sf ../../roles/*/module_utils/*.py .
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/bash -eu
|
||||
|
||||
git_version=$(git describe --tags | sed -e "s/^v//")
|
||||
version=${git_version%%-*}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Check which tests are scheduled to be executed."""
|
||||
|
||||
|
||||
24
utils/create_action_group.py
Normal file
24
utils/create_action_group.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import sys
|
||||
import yaml
|
||||
from facts import MANAGEMENT_MODULES
|
||||
|
||||
|
||||
def create_action_group(yml_file, project_prefix):
|
||||
yaml_data = None
|
||||
with open(yml_file) as f_in:
|
||||
yaml_data = yaml.safe_load(f_in)
|
||||
|
||||
yaml_data.setdefault("action_groups", {})[
|
||||
"%s.modules" % project_prefix
|
||||
] = MANAGEMENT_MODULES
|
||||
|
||||
with open(yml_file, 'w') as f_out:
|
||||
yaml.safe_dump(yaml_data, f_out, default_flow_style=False,
|
||||
explicit_start=True)
|
||||
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: %s <runtime file> <collection prefix>" % sys.argv[0])
|
||||
sys.exit(-1)
|
||||
|
||||
create_action_group(sys.argv[1], sys.argv[2])
|
||||
41
utils/facts.py
Normal file
41
utils/facts.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import os
|
||||
|
||||
|
||||
def get_roles(dir):
|
||||
roles = []
|
||||
|
||||
_rolesdir = "%s/roles/" % dir
|
||||
for _role in os.listdir(_rolesdir):
|
||||
_roledir = "%s/%s" % (_rolesdir, _role)
|
||||
if not os.path.isdir(_roledir) or \
|
||||
not os.path.isdir("%s/meta" % _roledir) or \
|
||||
not os.path.isdir("%s/tasks" % _roledir):
|
||||
continue
|
||||
roles.append(_role)
|
||||
|
||||
return sorted(roles)
|
||||
|
||||
|
||||
def get_modules(dir):
|
||||
management_modules = []
|
||||
roles_modules = []
|
||||
|
||||
for root, _dirs, files in os.walk(dir):
|
||||
if not root.startswith("%s/plugins/" % dir) and \
|
||||
not root.startswith("%s/roles/" % dir):
|
||||
continue
|
||||
for _file in files:
|
||||
if _file.endswith(".py"):
|
||||
if root == "%s/plugins/modules" % dir:
|
||||
management_modules.append(_file[:-3])
|
||||
elif root.startswith("%s/roles/" % dir):
|
||||
if root.endswith("/library"):
|
||||
roles_modules.append(_file[:-3])
|
||||
|
||||
return sorted(management_modules), sorted(roles_modules)
|
||||
|
||||
|
||||
BASE_DIR = os.path.abspath(os.path.dirname(__file__) + "/..")
|
||||
ROLES = get_roles(BASE_DIR)
|
||||
MANAGEMENT_MODULES, ROLES_MODULES = get_modules(BASE_DIR)
|
||||
ALL_MODULES = sorted(MANAGEMENT_MODULES + ROLES_MODULES)
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2019,2020 Red Hat
|
||||
# Copyright (C) 2019-2023 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
@@ -21,49 +21,95 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import re
|
||||
from facts import ROLES, ALL_MODULES
|
||||
|
||||
|
||||
def get_indent(txt):
|
||||
return len(txt) - len(txt.lstrip())
|
||||
|
||||
|
||||
def galaxyfy_playbook(project_prefix, collection_prefix, lines):
|
||||
po1 = re.compile('(%s.*:)$' % project_prefix)
|
||||
po2 = re.compile('(.*:) (%s.*)$' % project_prefix)
|
||||
po_module = re.compile('(%s.*):$' % project_prefix)
|
||||
po_module_arg = re.compile('(%s.*): (.*)$' % project_prefix)
|
||||
po_module_unnamed = re.compile('- (%s.*):$' % project_prefix)
|
||||
po_role = re.compile('(.*:) (%s.*)$' % project_prefix)
|
||||
|
||||
pattern_module = r'%s.\1:' % collection_prefix
|
||||
pattern_module_arg = r'%s.\1: \2' % collection_prefix
|
||||
pattern_module_unnamed = r'- %s.\1:' % collection_prefix
|
||||
pattern_role = r'\1 %s.\2' % collection_prefix
|
||||
|
||||
out_lines = []
|
||||
|
||||
pattern1 = r'%s.\1' % collection_prefix
|
||||
pattern2 = r'\1 %s.\2' % collection_prefix
|
||||
|
||||
changed = False
|
||||
changeable = False
|
||||
include_role = False
|
||||
module_defaults = False
|
||||
module_defaults_indent = -1
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("- name:") or \
|
||||
stripped.startswith("- block:"):
|
||||
changeable = True
|
||||
module_defaults = False
|
||||
module_defaults_indent = -1
|
||||
elif stripped in ["set_fact:", "ansible.builtin.set_fact:", "vars:"]:
|
||||
changeable = False
|
||||
include_role = False
|
||||
module_defaults = False
|
||||
module_defaults_indent = -1
|
||||
elif stripped == "roles:":
|
||||
changeable = True
|
||||
include_role = False
|
||||
module_defaults = False
|
||||
module_defaults_indent = -1
|
||||
elif (stripped.startswith("include_role:") or
|
||||
stripped.startswith("ansible.builtin.include_role:")):
|
||||
include_role = True
|
||||
module_defaults = False
|
||||
module_defaults_indent = -1
|
||||
elif include_role and stripped.startswith("name:"):
|
||||
line = po2.sub(pattern2, line)
|
||||
changed = True
|
||||
match = po_role.search(line)
|
||||
if match and match.group(2) in ROLES:
|
||||
line = po_role.sub(pattern_role, line)
|
||||
changed = True
|
||||
elif stripped == "module_defaults:":
|
||||
changeable = True
|
||||
include_role = False
|
||||
module_defaults = True
|
||||
module_defaults_indent = -1
|
||||
elif module_defaults:
|
||||
_indent = get_indent(line)
|
||||
if module_defaults_indent == -1:
|
||||
module_defaults_indent = _indent
|
||||
if _indent == module_defaults_indent:
|
||||
# only module, no YAML anchor or alias
|
||||
match = po_module.search(line)
|
||||
if match and match.group(1) in ALL_MODULES:
|
||||
line = po_module.sub(pattern_module, line)
|
||||
changed = True
|
||||
# module with YAML anchor or alias
|
||||
match = po_module_arg.search(line)
|
||||
if match and match.group(1) in ALL_MODULES:
|
||||
line = po_module_arg.sub(pattern_module_arg, line)
|
||||
changed = True
|
||||
elif changeable and stripped.startswith("- role:"):
|
||||
line = po2.sub(pattern2, line)
|
||||
changed = True
|
||||
match = po_role.search(line)
|
||||
if match and match.group(2) in ROLES:
|
||||
line = po_role.sub(pattern_role, line)
|
||||
changed = True
|
||||
elif (changeable and stripped.startswith(project_prefix)
|
||||
and not stripped.startswith(collection_prefix) # noqa
|
||||
and stripped.endswith(":")): # noqa
|
||||
line = po1.sub(pattern1, line)
|
||||
changed = True
|
||||
changeable = False # Only change first line in task
|
||||
match = po_module.search(line)
|
||||
if match and match.group(1) in ALL_MODULES:
|
||||
line = po_module.sub(pattern_module, line)
|
||||
changed = True
|
||||
changeable = False # Only change first line in task
|
||||
elif (stripped.startswith("- %s" % project_prefix)
|
||||
and stripped.endswith(":")): # noqa
|
||||
line = po1.sub(pattern1, line)
|
||||
changed = True
|
||||
match = po_module_unnamed.search(line)
|
||||
if match and match.group(1) in ALL_MODULES:
|
||||
line = po_module_unnamed.sub(pattern_module_unnamed, line)
|
||||
changed = True
|
||||
|
||||
out_lines.append(line)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/bash -eu
|
||||
|
||||
for i in roles/ipa*/*/*.py; do
|
||||
python utils/gen_module_docs.py "$i"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/bash -eu
|
||||
|
||||
INFO="\033[37;1m"
|
||||
WARN="\033[33;1m"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/bash -eu
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
@@ -63,7 +63,7 @@ for (( i=0; i<OPTIND-1; i++)); do
|
||||
shift
|
||||
done
|
||||
|
||||
if [ ${#@} -ne 3 ]; then
|
||||
if [ ${#@} -ne 4 ]; then
|
||||
usage;
|
||||
exit 1
|
||||
fi
|
||||
@@ -78,7 +78,7 @@ if [ -z "$name" ] || [ -z "$author" ] || [ -z "$email" ] || [ -z "$github_user"
|
||||
[ -z "$name" ] && echo "ERROR: name is not valid"
|
||||
[ -z "$author" ] && echo "ERROR: author is not valid"
|
||||
[ -z "$email" ] && echo "ERROR: email is not valid"
|
||||
[ -z "$githubuser" ] && echo "ERROR: github_user is not valid"
|
||||
[ -z "$github_user" ] && echo "ERROR: github_user is not valid"
|
||||
echo
|
||||
usage;
|
||||
exit 1;
|
||||
|
||||
@@ -45,7 +45,7 @@ Example playbook to make sure $name "NAME" is present:
|
||||
---
|
||||
- name: Playbook to manage IPA $name.
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- ipa$name:
|
||||
@@ -60,7 +60,7 @@ Example playbook to make sure $name "NAME" member PARAMETER2 VALUE is present:
|
||||
---
|
||||
- name: Playbook to manage IPA $name PARAMETER2 member.
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- ipa$name:
|
||||
@@ -78,7 +78,7 @@ Example playbook to make sure $name "NAME" member PARAMETER2 VALUE is absent:
|
||||
---
|
||||
- name: Playbook to manage IPA $name PARAMETER2 member.
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- ipa$name:
|
||||
@@ -96,7 +96,7 @@ Example playbook to make sure $name "NAME" is absent:
|
||||
---
|
||||
- name: Playbook to manage IPA $name.
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- ipa$name:
|
||||
@@ -117,7 +117,7 @@ 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 true. (bool) | no
|
||||
`name` \| `ALIAS` | The list of $name name strings. | yes
|
||||
`PARAMETER1` \| `API_PARAMETER_NAME` | DESCRIPTION | TYPE
|
||||
`PARAMETER2` \| `API_PARAMETER_NAME` | DESCRIPTION | TYPE
|
||||
|
||||
@@ -45,7 +45,7 @@ Example playbook to make sure $name "NAME" is present:
|
||||
---
|
||||
- name: Playbook to manage IPA $name.
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- ipa$name:
|
||||
@@ -61,7 +61,7 @@ Example playbook to make sure $name "NAME" is absent:
|
||||
---
|
||||
- name: Playbook to manage IPA $name.
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- ipa$name:
|
||||
@@ -82,7 +82,7 @@ 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 true. (bool) | no
|
||||
`name` \| `ALIAS` | The list of $name name strings. | yes
|
||||
`PARAMETER1` \| `API_PARAMETER_NAME` | DESCRIPTION | TYPE
|
||||
`PARAMETER2` \| `API_PARAMETER_NAME` | DESCRIPTION | TYPE
|
||||
|
||||
@@ -121,7 +121,7 @@ def main():
|
||||
argument_spec=dict(
|
||||
# general
|
||||
name=dict(type="list", elements="str", required=True,
|
||||
aliases=["API_PARAMETER_NAME"],
|
||||
aliases=["API_PARAMETER_NAME"]),
|
||||
# present
|
||||
PARAMETER1=dict(required=False, type='str',
|
||||
aliases=["API_PARAMETER_NAME"], default=None),
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
---
|
||||
- name: Test $name
|
||||
hosts: "{{ ipa_test_host | default('ipaserver') }}"
|
||||
# Change "become" or "gather_facts" to "yes",
|
||||
# if you test playbook requires any.
|
||||
become: no
|
||||
gather_facts: no
|
||||
# It is normally not needed to set "become" to "true" for a module test.
|
||||
# Only set it to true if it is needed to execute commands as root.
|
||||
become: false
|
||||
# Enable "gather_facts" only if "ansible_facts" variable needs to be used.
|
||||
gather_facts: false
|
||||
module_defaults:
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
|
||||
tasks:
|
||||
|
||||
@@ -12,7 +17,6 @@
|
||||
|
||||
- name: Ensure $name NAME is absent
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: NAME
|
||||
state: absent
|
||||
|
||||
@@ -22,8 +26,6 @@
|
||||
|
||||
- name: Ensure $name NAME is present
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: NAME
|
||||
# Add needed parameters here
|
||||
register: result
|
||||
@@ -31,8 +33,6 @@
|
||||
|
||||
- name: Ensure $name NAME is present again
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: NAME
|
||||
# Add needed parameters here
|
||||
register: result
|
||||
@@ -40,8 +40,6 @@
|
||||
|
||||
- name: Ensure $name NAME member PARAMETER2 VALUE is present
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: NAME
|
||||
PARAMETER2: VALUE
|
||||
action: member
|
||||
@@ -50,8 +48,6 @@
|
||||
|
||||
- name: Ensure $name NAME member PARAMETER2 VALUE is present again
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: NAME
|
||||
PARAMETER2: VALUE
|
||||
action: member
|
||||
@@ -60,8 +56,6 @@
|
||||
|
||||
- name: Ensure $name NAME member PARAMETER2 VALUE is absent
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: NAME
|
||||
PARAMETER2: VALUE
|
||||
action: member
|
||||
@@ -71,8 +65,6 @@
|
||||
|
||||
- name: Ensure $name NAME member PARAMETER2 VALUE is absent again
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: NAME
|
||||
PARAMETER2: VALUE
|
||||
action: member
|
||||
@@ -84,8 +76,6 @@
|
||||
|
||||
- name: Ensure $name NAME is absent
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: NAME
|
||||
state: absent
|
||||
register: result
|
||||
@@ -93,8 +83,6 @@
|
||||
|
||||
- name: Ensure $name NAME is absent again
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: NAME
|
||||
state: absent
|
||||
register: result
|
||||
@@ -104,7 +92,5 @@
|
||||
|
||||
- name: Ensure $name NAME is absent
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: NAME
|
||||
state: absent
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
---
|
||||
- name: Test $name
|
||||
hosts: "{{ ipa_test_host | default('ipaserver') }}"
|
||||
# Change "become" or "gather_facts" to "yes",
|
||||
# if you test playbook requires any.
|
||||
become: no
|
||||
gather_facts: no
|
||||
# It is normally not needed to set "become" to "true" for a module test.
|
||||
# Only set it to true if it is needed to execute commands as root.
|
||||
become: false
|
||||
# Enable "gather_facts" only if "ansible_facts" variable needs to be used.
|
||||
gather_facts: false
|
||||
module_defaults:
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
|
||||
tasks:
|
||||
|
||||
@@ -12,8 +17,6 @@
|
||||
|
||||
- name: Ensure $name NAME is absent
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: NAME
|
||||
state: absent
|
||||
|
||||
@@ -23,7 +26,6 @@
|
||||
|
||||
- name: Ensure $name NAME is present
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: NAME
|
||||
# Add needed parameters here
|
||||
register: result
|
||||
@@ -31,8 +33,6 @@
|
||||
|
||||
- name: Ensure $name NAME is present again
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: NAME
|
||||
# Add needed parameters here
|
||||
register: result
|
||||
@@ -42,8 +42,6 @@
|
||||
|
||||
- name: Ensure $name NAME is absent
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: NAME
|
||||
state: absent
|
||||
register: result
|
||||
@@ -51,8 +49,6 @@
|
||||
|
||||
- name: Ensure $name NAME is absent again
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: NAME
|
||||
state: absent
|
||||
register: result
|
||||
@@ -62,6 +58,5 @@
|
||||
|
||||
- name: Ensure $name NAME is absent
|
||||
ipa$name:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: NAME
|
||||
state: absent
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
---
|
||||
- name: Test ${name}
|
||||
hosts: ipaclients, ipaserver
|
||||
# Change "become" or "gather_facts" to "yes",
|
||||
# if you test playbook requires any.
|
||||
become: no
|
||||
gather_facts: no
|
||||
# It is normally not needed to set "become" to "true" for a module test.
|
||||
# Only set it to true if it is needed to execute commands as root.
|
||||
become: false
|
||||
# Enable "gather_facts" only if "ansible_facts" variable needs to be used.
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: Include FreeIPA facts.
|
||||
|
||||
Reference in New Issue
Block a user