mirror of
https://github.com/freeipa/ansible-freeipa.git
synced 2026-03-27 13:53:06 +00:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c251632368 | ||
|
|
452d20e28d | ||
|
|
c7699472a6 | ||
|
|
eba457d5ff | ||
|
|
809e423947 | ||
|
|
e5f0ab2fe4 | ||
|
|
f85c60676c | ||
|
|
f9bf0cfec0 | ||
|
|
8f0d983845 | ||
|
|
aed5edae33 | ||
|
|
889b2a5576 | ||
|
|
e9d637c57a | ||
|
|
b3a97eacec | ||
|
|
aa745100e3 | ||
|
|
23faa83a0b | ||
|
|
12729fc2c0 | ||
|
|
31810ad7c0 | ||
|
|
9dcff9a308 | ||
|
|
e500c133c0 | ||
|
|
a5306b2db5 | ||
|
|
8ab3aa06ff | ||
|
|
87ff15a92c | ||
|
|
c8d5cb7ee2 | ||
|
|
2fa4aa60b1 | ||
|
|
4332636fd2 | ||
|
|
266f79b55f | ||
|
|
07b056ad25 | ||
|
|
7db5d59de1 | ||
|
|
e19e16c734 | ||
|
|
0ff119a2a8 | ||
|
|
90f6e14c40 | ||
|
|
e044310dad | ||
|
|
4be7a9fba0 | ||
|
|
98959807d2 | ||
|
|
a16379cfa0 | ||
|
|
672413f4dd | ||
|
|
8af4329fac | ||
|
|
9932b1dc98 | ||
|
|
1c44898e68 | ||
|
|
f44dc55b90 | ||
|
|
65b106449e | ||
|
|
7501c84844 | ||
|
|
d45e6ac399 | ||
|
|
d990832681 | ||
|
|
b998597815 | ||
|
|
d51ee9dc69 | ||
|
|
fdfea1b6fb | ||
|
|
ac92ed1408 | ||
|
|
757b89dfae | ||
|
|
914e4879f8 | ||
|
|
13cff6354b | ||
|
|
4ff5aaa172 | ||
|
|
d82abdbef9 | ||
|
|
5aa80204d5 | ||
|
|
8b8cbdd8c2 | ||
|
|
a06b16f5bc | ||
|
|
dc99b821eb | ||
|
|
796f84357a | ||
|
|
9e6c79abbb | ||
|
|
d3af87c731 | ||
|
|
7011283335 | ||
|
|
0297cbe973 | ||
|
|
1ec0d1e640 |
@@ -175,8 +175,8 @@ Variable | Description | Required
|
||||
`rid_base` \| `ipabaserid` | First RID of the corresponding RID range. (int) | no
|
||||
`secondary_rid_base` \| `ipasecondarybaserid` | First RID of the secondary RID range. (int) | no
|
||||
`dom_sid` \| `ipanttrusteddomainsid` | Domain SID of the trusted domain. | no
|
||||
`dom_name` \| `ipanttrusteddomainname` | Name of the trusted domain. | no
|
||||
`idrange_type` \| `iparangetype` | ID range type, one of `ipa-ad-trust`, `ipa-ad-trust-posix`, `ipa-local`. Only valid if idrange does not exist. | no
|
||||
`dom_name` \| `ipanttrusteddomainname` | Name of the trusted domain. Can only be used when `ipaapi_context: server`. | no
|
||||
`auto_private_groups` \| `ipaautoprivategroups` | Auto creation of private groups, one of `true`, `false`, `hybrid`. | no
|
||||
`delete_continue` \| `continue` | Continuous mode: don't stop on errors. Valid only if `state` is `absent`. Default: `no` (bool) | no
|
||||
`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no
|
||||
|
||||
@@ -12,6 +12,7 @@ Features
|
||||
* One-time-password (OTP) support for client installation
|
||||
* Repair mode for clients
|
||||
* Backup and restore, also to and from controller
|
||||
* Smartcard setup for servers and clients
|
||||
* Modules for automembership rule management
|
||||
* Modules for automount key management
|
||||
* Modules for automount location management
|
||||
@@ -425,6 +426,8 @@ Roles
|
||||
* [Replica](roles/ipareplica/README.md)
|
||||
* [Client](roles/ipaclient/README.md)
|
||||
* [Backup](roles/ipabackup/README.md)
|
||||
* [SmartCard server](roles/ipasmartcard_server/README.md)
|
||||
* [SmartCard client](roles/ipasmartcard_client/README.md)
|
||||
|
||||
Modules in plugin/modules
|
||||
=========================
|
||||
|
||||
@@ -1 +1 @@
|
||||
centos-8
|
||||
fedora-latest
|
||||
30
molecule/fedora-rawhide-build/Dockerfile
Normal file
30
molecule/fedora-rawhide-build/Dockerfile
Normal file
@@ -0,0 +1,30 @@
|
||||
FROM fedora:rawhide
|
||||
ENV container=docker
|
||||
|
||||
RUN rm -fv /var/cache/dnf/metadata_lock.pid; \
|
||||
dnf makecache; \
|
||||
dnf --assumeyes install \
|
||||
/usr/bin/python3 \
|
||||
/usr/bin/python3-config \
|
||||
/usr/bin/dnf-3 \
|
||||
sudo \
|
||||
bash \
|
||||
systemd \
|
||||
procps-ng \
|
||||
iproute && \
|
||||
dnf clean all; \
|
||||
(cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
|
||||
rm -f /lib/systemd/system/multi-user.target.wants/*;\
|
||||
rm -f /etc/systemd/system/*.wants/*;\
|
||||
rm -f /lib/systemd/system/local-fs.target.wants/*; \
|
||||
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
|
||||
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
|
||||
rm -f /lib/systemd/system/basic.target.wants/*;\
|
||||
rm -f /lib/systemd/system/anaconda.target.wants/*; \
|
||||
rm -rf /var/cache/dnf/;
|
||||
|
||||
STOPSIGNAL RTMIN+3
|
||||
|
||||
VOLUME ["/sys/fs/cgroup"]
|
||||
|
||||
CMD ["/usr/sbin/init"]
|
||||
@@ -2,9 +2,9 @@
|
||||
driver:
|
||||
name: docker
|
||||
platforms:
|
||||
- name: centos-8-build
|
||||
image: "centos:centos8"
|
||||
pre_build_image: true
|
||||
- name: fedora-rawhide-build
|
||||
image: "fedora:rawhide"
|
||||
dockerfile: Dockerfile
|
||||
hostname: ipaserver.test.local
|
||||
dns_servers:
|
||||
- 8.8.8.8
|
||||
@@ -2,8 +2,8 @@
|
||||
driver:
|
||||
name: docker
|
||||
platforms:
|
||||
- name: centos-8
|
||||
image: quay.io/ansible-freeipa/upstream-tests:centos-8
|
||||
- name: fedora-rawhide
|
||||
image: quay.io/ansible-freeipa/upstream-tests:fedora-rawhide
|
||||
pre_build_image: true
|
||||
hostname: ipaserver.test.local
|
||||
dns_servers:
|
||||
8
playbooks/install-smartcard-clients.yml
Normal file
8
playbooks/install-smartcard-clients.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
- name: Playbook to setup smartcard for IPA clients
|
||||
hosts: ipaclients
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- role: ipasmartcard_client
|
||||
state: present
|
||||
8
playbooks/install-smartcard-replicas.yml
Normal file
8
playbooks/install-smartcard-replicas.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
- name: Playbook to setup smartcard for IPA replicas
|
||||
hosts: ipareplicas
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- role: ipasmartcard_server
|
||||
state: present
|
||||
8
playbooks/install-smartcard-server.yml
Normal file
8
playbooks/install-smartcard-server.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
- name: Playbook to setup smartcard for IPA server
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- role: ipasmartcard_server
|
||||
state: present
|
||||
8
playbooks/install-smartcard-servers.yml
Normal file
8
playbooks/install-smartcard-servers.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
- name: Playbook to setup smartcard for IPA server and replicas
|
||||
hosts: ipaserver, ipareplicas
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- role: ipasmartcard_server
|
||||
state: present
|
||||
@@ -28,8 +28,8 @@ __metaclass__ = type
|
||||
__all__ = ["gssapi", "netaddr", "api", "ipalib_errors", "Env",
|
||||
"DEFAULT_CONFIG", "LDAP_GENERALIZED_TIME_FORMAT",
|
||||
"kinit_password", "kinit_keytab", "run", "DN", "VERSION",
|
||||
"paths", "get_credentials_if_valid", "Encoding",
|
||||
"load_pem_x509_certificate", "DNSName"]
|
||||
"paths", "tasks", "get_credentials_if_valid", "Encoding",
|
||||
"load_pem_x509_certificate", "DNSName", "getargspec"]
|
||||
|
||||
import sys
|
||||
|
||||
@@ -48,29 +48,32 @@ else:
|
||||
import gssapi
|
||||
from datetime import datetime
|
||||
from contextlib import contextmanager
|
||||
import inspect
|
||||
|
||||
# Import getargspec from inspect or provide own getargspec for
|
||||
# Python 2 compatibility with Python 3.11+.
|
||||
try:
|
||||
from inspect import getargspec
|
||||
except ImportError:
|
||||
from collections import namedtuple
|
||||
from inspect import getfullargspec
|
||||
|
||||
# The code is copied from Python 3.10 inspect.py
|
||||
# Authors: Ka-Ping Yee <ping@lfw.org>
|
||||
# Yury Selivanov <yselivanov@sprymix.com>
|
||||
ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults')
|
||||
|
||||
def getargspec(func):
|
||||
args, varargs, varkw, defaults, kwonlyargs, _kwonlydefaults, \
|
||||
ann = getfullargspec(func)
|
||||
if kwonlyargs or ann:
|
||||
raise ValueError(
|
||||
"Function has keyword-only parameters or annotations"
|
||||
", use inspect.signature() API which can support them")
|
||||
return ArgSpec(args, varargs, varkw, defaults)
|
||||
|
||||
# ansible-freeipa requires locale to be C, IPA requires utf-8.
|
||||
os.environ["LANGUAGE"] = "C"
|
||||
|
||||
try:
|
||||
from packaging import version
|
||||
except ImportError:
|
||||
# If `packaging` not found, split version string for creating version
|
||||
# object. Although it is not PEP 440 compliant, it will work for stable
|
||||
# FreeIPA releases.
|
||||
import re
|
||||
|
||||
class version: # pylint: disable=invalid-name, too-few-public-methods
|
||||
@staticmethod
|
||||
def parse(version_str):
|
||||
"""
|
||||
Split a version string A.B.C, into a tuple.
|
||||
|
||||
This will not work for `rc`, `dev` or similar version string.
|
||||
"""
|
||||
return tuple(re.split("[-_.]", version_str)) # noqa: W605
|
||||
|
||||
from ipalib import api
|
||||
from ipalib import errors as ipalib_errors # noqa
|
||||
from ipalib.config import Env
|
||||
@@ -84,6 +87,7 @@ else:
|
||||
from ipapython.dn import DN
|
||||
from ipapython.version import VERSION
|
||||
from ipaplatform.paths import paths
|
||||
from ipaplatform.tasks import tasks
|
||||
from ipalib.krb_utils import get_credentials_if_valid
|
||||
from ipapython.dnsutil import DNSName
|
||||
from ipapython import kerberos
|
||||
@@ -139,6 +143,13 @@ else:
|
||||
|
||||
return fstore.has_files()
|
||||
|
||||
# Try to import dcerpc
|
||||
try:
|
||||
import ipaserver.dcerpc # pylint: disable=no-member
|
||||
_dcerpc_bindings_installed = True # pylint: disable=invalid-name
|
||||
except ImportError:
|
||||
_dcerpc_bindings_installed = False # pylint: disable=invalid-name
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
@@ -221,6 +232,8 @@ else:
|
||||
ldap_cache: Control use of LDAP cache layer. (bool)
|
||||
|
||||
"""
|
||||
global _dcerpc_bindings_installed # pylint: disable=C0103,W0603
|
||||
|
||||
env = Env()
|
||||
env._bootstrap()
|
||||
env._finalize_core(**dict(DEFAULT_CONFIG))
|
||||
@@ -252,6 +265,7 @@ else:
|
||||
backend = api.Backend.ldap2
|
||||
else:
|
||||
backend = api.Backend.rpcclient
|
||||
_dcerpc_bindings_installed = False
|
||||
|
||||
if not backend.isconnected():
|
||||
backend.connect(ccache=os.environ.get('KRB5CCNAME', None))
|
||||
@@ -289,8 +303,8 @@ else:
|
||||
operation = oper_map.get(oper)
|
||||
if not operation:
|
||||
raise NotImplementedError("Invalid operator: %s" % oper)
|
||||
return operation(version.parse(VERSION),
|
||||
version.parse(requested_version))
|
||||
return operation(tasks.parse_ipa_version(VERSION),
|
||||
tasks.parse_ipa_version(requested_version))
|
||||
|
||||
def date_format(value):
|
||||
accepted_date_formats = [
|
||||
@@ -701,6 +715,42 @@ else:
|
||||
print(jsonify(kwargs))
|
||||
sys.exit(0)
|
||||
|
||||
def __get_domain_validator():
|
||||
if not _dcerpc_bindings_installed:
|
||||
raise ipalib_errors.NotFound(
|
||||
reason=(
|
||||
'Cannot perform SID validation without Samba 4 support '
|
||||
'installed. Make sure you have installed server-trust-ad '
|
||||
'sub-package of IPA on the server'
|
||||
)
|
||||
)
|
||||
|
||||
# pylint: disable=no-member
|
||||
domain_validator = ipaserver.dcerpc.DomainValidator(api)
|
||||
# pylint: enable=no-member
|
||||
|
||||
if not domain_validator.is_configured():
|
||||
raise ipalib_errors.NotFound(
|
||||
reason=(
|
||||
'Cross-realm trusts are not configured. Make sure you '
|
||||
'have run ipa-adtrust-install on the IPA server first'
|
||||
)
|
||||
)
|
||||
|
||||
return domain_validator
|
||||
|
||||
def get_trusted_domain_sid_from_name(dom_name):
|
||||
"""
|
||||
Given a trust domain name, returns the domain SID.
|
||||
|
||||
Returns unicode string representation for a given trusted domain name
|
||||
or None if SID for the given trusted domain name could not be found.
|
||||
"""
|
||||
domain_validator = __get_domain_validator()
|
||||
sid = domain_validator.get_sid_from_domain_name(dom_name)
|
||||
|
||||
return unicode(sid) if sid is not None else None
|
||||
|
||||
class IPAParamMapping(Mapping):
|
||||
"""
|
||||
Provides IPA API mapping to playbook parameters or computed values.
|
||||
@@ -799,7 +849,10 @@ else:
|
||||
# Check if param_name is actually a param
|
||||
if param_name in self.ansible_module.params:
|
||||
value = self.ansible_module.params_get(param_name)
|
||||
if isinstance(value, bool):
|
||||
if (
|
||||
self.ansible_module.ipa_check_version("<", "4.9.10")
|
||||
and isinstance(value, bool)
|
||||
):
|
||||
value = "TRUE" if value else "FALSE"
|
||||
|
||||
# Since param wasn't a param check if it's a method name
|
||||
@@ -1182,7 +1235,7 @@ else:
|
||||
elif result_handler is not None:
|
||||
if "errors" not in handlers_user_args:
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(result_handler)
|
||||
argspec = getargspec(result_handler)
|
||||
if "errors" in argspec.args:
|
||||
handlers_user_args["errors"] = _errors
|
||||
|
||||
|
||||
@@ -441,7 +441,11 @@ def main():
|
||||
elif (
|
||||
isinstance(value, (tuple, list)) and arg_type == "bool"
|
||||
):
|
||||
exit_args[k] = (value[0] == "TRUE")
|
||||
# FreeIPA 4.9.10+ and 4.10 use proper mapping for
|
||||
# boolean values, so we need to convert it to str
|
||||
# for comparison.
|
||||
# See: https://github.com/freeipa/freeipa/pull/6294
|
||||
exit_args[k] = (str(value[0]).upper() == "TRUE")
|
||||
else:
|
||||
if arg_type not in type_map:
|
||||
raise ValueError(
|
||||
|
||||
@@ -173,7 +173,10 @@ def gen_args(module, state, action, dnsconfig, forwarders, forward_policy,
|
||||
_args['idnsforwardpolicy'] = forward_policy
|
||||
|
||||
if allow_sync_ptr is not None:
|
||||
_args['idnsallowsyncptr'] = 'TRUE' if allow_sync_ptr else 'FALSE'
|
||||
if module.ipa_check_version("<", "4.9.10"):
|
||||
_args['idnsallowsyncptr'] = "TRUE" if allow_sync_ptr else "FALSE"
|
||||
else:
|
||||
_args['idnsallowsyncptr'] = allow_sync_ptr
|
||||
|
||||
return _args
|
||||
|
||||
@@ -199,7 +202,8 @@ def main():
|
||||
choices=["member", "dnsconfig"]),
|
||||
state=dict(type="str", default="present",
|
||||
choices=["present", "absent"]),
|
||||
)
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
|
||||
@@ -344,7 +344,13 @@ def main():
|
||||
|
||||
if state in ['enabled', 'disabled']:
|
||||
if existing_resource is not None:
|
||||
is_enabled = existing_resource["idnszoneactive"][0]
|
||||
# FreeIPA 4.9.10+ and 4.10 use proper mapping for
|
||||
# boolean values, so we need to convert it to str
|
||||
# for comparison.
|
||||
# See: https://github.com/freeipa/freeipa/pull/6294
|
||||
is_enabled = (
|
||||
str(existing_resource["idnszoneactive"][0]).upper()
|
||||
)
|
||||
else:
|
||||
ansible_module.fail_json(
|
||||
msg="dnsforwardzone '%s' not found." % (name))
|
||||
|
||||
@@ -418,7 +418,11 @@ class DNSZoneModule(IPAAnsibleModule):
|
||||
is_zone_active = False
|
||||
else:
|
||||
zone = response["result"]
|
||||
is_zone_active = "TRUE" in zone.get("idnszoneactive")
|
||||
# FreeIPA 4.9.10+ and 4.10 use proper mapping for boolean vaalues.
|
||||
# See: https://github.com/freeipa/freeipa/pull/6294
|
||||
is_zone_active = (
|
||||
str(zone.get("idnszoneactive")[0]).upper() == "TRUE"
|
||||
)
|
||||
|
||||
return zone, is_zone_active
|
||||
|
||||
|
||||
@@ -472,18 +472,26 @@ def main():
|
||||
# hbacrule_enable is not failing on an enabled hbacrule
|
||||
# Therefore it is needed to have a look at the ipaenabledflag
|
||||
# in res_find.
|
||||
if "ipaenabledflag" not in res_find or \
|
||||
res_find["ipaenabledflag"][0] != "TRUE":
|
||||
# FreeIPA 4.9.10+ and 4.10 use proper mapping for
|
||||
# boolean values, so we need to convert it to str
|
||||
# for comparison.
|
||||
# See: https://github.com/freeipa/freeipa/pull/6294
|
||||
enabled_flag = str(res_find.get("ipaenabledflag", [False])[0])
|
||||
if enabled_flag.upper() != "TRUE":
|
||||
commands.append([name, "hbacrule_enable", {}])
|
||||
|
||||
elif state == "disabled":
|
||||
if res_find is None:
|
||||
ansible_module.fail_json(msg="No hbacrule '%s'" % name)
|
||||
# hbacrule_disable is not failing on an disabled hbacrule
|
||||
# hbacrule_disable is not failing on an enabled hbacrule
|
||||
# Therefore it is needed to have a look at the ipaenabledflag
|
||||
# in res_find.
|
||||
if "ipaenabledflag" not in res_find or \
|
||||
res_find["ipaenabledflag"][0] != "FALSE":
|
||||
# FreeIPA 4.9.10+ and 4.10 use proper mapping for
|
||||
# boolean values, so we need to convert it to str
|
||||
# for comparison.
|
||||
# See: https://github.com/freeipa/freeipa/pull/6294
|
||||
enabled_flag = str(res_find.get("ipaenabledflag", [False])[0])
|
||||
if enabled_flag.upper() != "FALSE":
|
||||
commands.append([name, "hbacrule_disable", {}])
|
||||
|
||||
else:
|
||||
|
||||
@@ -74,7 +74,9 @@ options:
|
||||
required: false
|
||||
aliases: ["ipanttrusteddomainsid"]
|
||||
dom_name:
|
||||
description: Domain name of the trusted domain.
|
||||
description: |
|
||||
Domain name of the trusted domain. Can only be used when
|
||||
`ipaapi_context: server`.
|
||||
type: string
|
||||
required: false
|
||||
aliases: ["ipanttrusteddomainname"]
|
||||
@@ -134,7 +136,7 @@ RETURN = """
|
||||
|
||||
|
||||
from ansible.module_utils.ansible_freeipa_module import \
|
||||
IPAAnsibleModule, compare_args_ipa
|
||||
IPAAnsibleModule, compare_args_ipa, get_trusted_domain_sid_from_name
|
||||
from ansible.module_utils import six
|
||||
|
||||
if six.PY3:
|
||||
@@ -154,7 +156,7 @@ def find_idrange(module, name):
|
||||
|
||||
def gen_args(
|
||||
base_id, range_size, rid_base, secondary_rid_base, idrange_type, dom_sid,
|
||||
auto_private_groups
|
||||
dom_name, auto_private_groups
|
||||
):
|
||||
_args = {}
|
||||
# Integer parameters are stored as strings.
|
||||
@@ -169,6 +171,8 @@ def gen_args(
|
||||
_args["ipasecondarybaserid"] = secondary_rid_base
|
||||
if idrange_type is not None:
|
||||
_args["iparangetype"] = idrange_type
|
||||
if dom_name is not None:
|
||||
dom_sid = get_trusted_domain_sid_from_name(dom_name)
|
||||
if dom_sid is not None:
|
||||
_args["ipanttrusteddomainsid"] = dom_sid
|
||||
if auto_private_groups is not None:
|
||||
@@ -230,6 +234,7 @@ def main():
|
||||
secondary_rid_base = ansible_module.params_get("secondary_rid_base")
|
||||
idrange_type = ansible_module.params_get("idrange_type")
|
||||
dom_sid = ansible_module.params_get("dom_sid")
|
||||
dom_name = ansible_module.params_get("dom_name")
|
||||
auto_private_groups = \
|
||||
ansible_module.params_get_lowercase("auto_private_groups")
|
||||
|
||||
@@ -248,7 +253,10 @@ def main():
|
||||
if state == "absent":
|
||||
if len(names) < 1:
|
||||
ansible_module.fail_json(msg="No name given.")
|
||||
invalid = ["base_id", "range_size", "idrange_type", "dom_sid"]
|
||||
invalid = [
|
||||
"base_id", "range_size", "idrange_type", "dom_sid", "dom_name",
|
||||
"rid_base", "secondary_rid_base", "auto_private_groups"
|
||||
]
|
||||
|
||||
ansible_module.params_fail_used_invalid(invalid, state)
|
||||
|
||||
@@ -278,7 +286,7 @@ def main():
|
||||
# Generate args
|
||||
args = gen_args(
|
||||
base_id, range_size, rid_base, secondary_rid_base,
|
||||
idrange_type, dom_sid, auto_private_groups
|
||||
idrange_type, dom_sid, dom_name, auto_private_groups
|
||||
)
|
||||
|
||||
# Found the idrange
|
||||
|
||||
@@ -656,8 +656,12 @@ def main():
|
||||
# sudorule_enable is not failing on an enabled sudorule
|
||||
# Therefore it is needed to have a look at the ipaenabledflag
|
||||
# in res_find.
|
||||
if "ipaenabledflag" not in res_find or \
|
||||
res_find["ipaenabledflag"][0] != "TRUE":
|
||||
# FreeIPA 4.9.10+ and 4.10 use proper mapping for
|
||||
# boolean values, so we need to convert it to str
|
||||
# for comparison.
|
||||
# See: https://github.com/freeipa/freeipa/pull/6294
|
||||
enabled_flag = str(res_find.get("ipaenabledflag", [False])[0])
|
||||
if enabled_flag.upper() != "TRUE":
|
||||
commands.append([name, "sudorule_enable", {}])
|
||||
|
||||
elif state == "disabled":
|
||||
@@ -666,8 +670,12 @@ def main():
|
||||
# sudorule_disable is not failing on an disabled sudorule
|
||||
# Therefore it is needed to have a look at the ipaenabledflag
|
||||
# in res_find.
|
||||
if "ipaenabledflag" not in res_find or \
|
||||
res_find["ipaenabledflag"][0] != "FALSE":
|
||||
# FreeIPA 4.9.10+ and 4.10 use proper mapping for
|
||||
# boolean values, so we need to convert it to str
|
||||
# for comparison.
|
||||
# See: https://github.com/freeipa/freeipa/pull/6294
|
||||
enabled_flag = str(res_find.get("ipaenabledflag", [False])[0])
|
||||
if enabled_flag.upper() != "FALSE":
|
||||
commands.append([name, "sudorule_disable", {}])
|
||||
|
||||
else:
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
-r requirements-tests.txt
|
||||
ipdb
|
||||
ipdb==0.13.4
|
||||
pre-commit
|
||||
flake8==4.0.1
|
||||
flake8-bugbear
|
||||
pylint==2.12.2
|
||||
pylint==2.13.7
|
||||
pydocstyle==6.0.0
|
||||
yamllint==1.26.3
|
||||
ansible-lint==5.3.2
|
||||
dnspython==2.2.0
|
||||
netaddr==0.8.0
|
||||
gssapi==1.7.2
|
||||
|
||||
@@ -75,7 +75,6 @@ subject_base:
|
||||
'''
|
||||
|
||||
import os
|
||||
import inspect
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
@@ -83,7 +82,7 @@ from ansible.module_utils.ansible_ipa_client import (
|
||||
paths, x509, NUM_VERSION, serialization, certdb, api,
|
||||
delete_persistent_client_session_data, write_tmp_file,
|
||||
ipa_generate_password, CalledProcessError, errors, disable_ra, DN,
|
||||
CLIENT_INSTALL_ERROR, logger
|
||||
CLIENT_INSTALL_ERROR, logger, getargspec
|
||||
)
|
||||
|
||||
|
||||
@@ -134,7 +133,7 @@ def main():
|
||||
# Add CA certs to a temporary NSS database
|
||||
try:
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(tmp_db.create_db)
|
||||
argspec = getargspec(tmp_db.create_db)
|
||||
# pylint: enable=deprecated-method
|
||||
if "password_filename" not in argspec.args:
|
||||
tmp_db.create_db()
|
||||
|
||||
@@ -57,11 +57,10 @@ EXAMPLES = '''
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
import inspect
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
setup_logging, options, sysrestore, paths, configure_nisdomain
|
||||
setup_logging, options, sysrestore, paths, configure_nisdomain,
|
||||
getargspec
|
||||
)
|
||||
|
||||
|
||||
@@ -83,7 +82,7 @@ def main():
|
||||
statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE)
|
||||
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(configure_nisdomain)
|
||||
argspec = getargspec(configure_nisdomain)
|
||||
# pylint: enable=deprecated-method
|
||||
if "statestore" not in argspec.args:
|
||||
# NUM_VERSION < 40500:
|
||||
|
||||
@@ -141,7 +141,6 @@ RETURN = '''
|
||||
|
||||
import os
|
||||
import time
|
||||
import inspect
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
@@ -151,7 +150,7 @@ from ansible.module_utils.ansible_ipa_client import (
|
||||
get_certs_from_ldap, DN, certstore, x509, logger, certdb,
|
||||
CalledProcessError, tasks, client_dns, configure_certmonger, services,
|
||||
update_ssh_keys, save_state, configure_ldap_conf, configure_nslcd_conf,
|
||||
configure_openldap_conf, hardcode_ldap_server
|
||||
configure_openldap_conf, hardcode_ldap_server, getargspec
|
||||
)
|
||||
|
||||
|
||||
@@ -323,7 +322,7 @@ def main():
|
||||
pass
|
||||
|
||||
# pylint: disable=deprecated-method
|
||||
argspec_save_state = inspect.getargspec(save_state)
|
||||
argspec_save_state = getargspec(save_state)
|
||||
|
||||
# Name Server Caching Daemon. Disable for SSSD, use otherwise
|
||||
# (if installed)
|
||||
@@ -387,7 +386,7 @@ def main():
|
||||
if not options.no_ac:
|
||||
# Modify nsswitch/pam stack
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(tasks.modify_nsswitch_pam_stack)
|
||||
argspec = getargspec(tasks.modify_nsswitch_pam_stack)
|
||||
if "sudo" in argspec.args:
|
||||
tasks.modify_nsswitch_pam_stack(
|
||||
sssd=options.sssd,
|
||||
|
||||
@@ -66,13 +66,11 @@ EXAMPLES = '''
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
import inspect
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
setup_logging,
|
||||
options, sysrestore, paths, sync_time, logger, ipadiscovery,
|
||||
timeconf
|
||||
timeconf, getargspec
|
||||
)
|
||||
|
||||
|
||||
@@ -114,7 +112,7 @@ def main():
|
||||
if options.conf_ntp:
|
||||
# Attempt to configure and sync time with NTP server (chrony).
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(sync_time)
|
||||
argspec = getargspec(sync_time)
|
||||
# pylint: enable=deprecated-method
|
||||
if "options" not in argspec.args:
|
||||
synced_ntp = sync_time(options.ntp_servers, options.ntp_pool,
|
||||
|
||||
@@ -197,7 +197,6 @@ nosssd_files:
|
||||
|
||||
import os
|
||||
import socket
|
||||
import inspect
|
||||
|
||||
try:
|
||||
from ansible.module_utils.six.moves.configparser import RawConfigParser
|
||||
@@ -212,7 +211,7 @@ from ansible.module_utils.ansible_ipa_client import (
|
||||
CLIENT_INSTALL_ERROR, tasks, check_ldap_conf, timeconf, constants,
|
||||
validate_hostname, nssldap_exists, gssapi, remove_file,
|
||||
check_ip_addresses, ipadiscovery, print_port_conf_info,
|
||||
IPA_PYTHON_VERSION
|
||||
IPA_PYTHON_VERSION, getargspec
|
||||
)
|
||||
|
||||
|
||||
@@ -344,7 +343,7 @@ def main():
|
||||
|
||||
if options.realm_name:
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(validate_domain_name)
|
||||
argspec = getargspec(validate_domain_name)
|
||||
if "entity" in argspec.args:
|
||||
# NUM_VERSION >= 40690:
|
||||
validate_domain_name(options.realm_name, entity="realm")
|
||||
@@ -881,7 +880,6 @@ def main():
|
||||
is_ipaddr = False
|
||||
|
||||
if is_ipaddr:
|
||||
logger.info()
|
||||
logger.warning(
|
||||
"It seems that you are using an IP address "
|
||||
"instead of FQDN as an argument to --server. The "
|
||||
|
||||
@@ -46,7 +46,7 @@ __all__ = ["gssapi", "version", "ipadiscovery", "api", "errors", "x509",
|
||||
"configure_nslcd_conf", "configure_ssh_config",
|
||||
"configure_sshd_config", "configure_automount",
|
||||
"configure_firefox", "sync_time", "check_ldap_conf",
|
||||
"sssd_enable_ifp"]
|
||||
"sssd_enable_ifp", "getargspec"]
|
||||
|
||||
import sys
|
||||
|
||||
@@ -110,10 +110,31 @@ else:
|
||||
# IPA version >= 4.4
|
||||
|
||||
# import sys
|
||||
import inspect
|
||||
import gssapi
|
||||
import logging
|
||||
|
||||
# Import getargspec from inspect or provide own getargspec for
|
||||
# Python 2 compatibility with Python 3.11+.
|
||||
try:
|
||||
from inspect import getargspec
|
||||
except ImportError:
|
||||
from collections import namedtuple
|
||||
from inspect import getfullargspec
|
||||
|
||||
# The code is copied from Python 3.10 inspect.py
|
||||
# Authors: Ka-Ping Yee <ping@lfw.org>
|
||||
# Yury Selivanov <yselivanov@sprymix.com>
|
||||
ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults')
|
||||
|
||||
def getargspec(func):
|
||||
args, varargs, varkw, defaults, kwonlyargs, _kwonlydefaults, \
|
||||
ann = getfullargspec(func)
|
||||
if kwonlyargs or ann:
|
||||
raise ValueError(
|
||||
"Function has keyword-only parameters or annotations"
|
||||
", use inspect.signature() API which can support them")
|
||||
return ArgSpec(args, varargs, varkw, defaults)
|
||||
|
||||
from ipapython import version
|
||||
try:
|
||||
from ipaclient.install import ipadiscovery
|
||||
@@ -200,7 +221,7 @@ else:
|
||||
sys.path.remove(temp_dir)
|
||||
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(
|
||||
argspec = getargspec(
|
||||
ipa_client_install.configure_krb5_conf)
|
||||
if argspec.keywords is None:
|
||||
def configure_krb5_conf(
|
||||
@@ -240,7 +261,7 @@ else:
|
||||
create_ipa_nssdb = certdb.create_ipa_nssdb
|
||||
|
||||
argspec = \
|
||||
inspect.getargspec(ipa_client_install.configure_nisdomain)
|
||||
getargspec(ipa_client_install.configure_nisdomain)
|
||||
if len(argspec.args) == 3:
|
||||
configure_nisdomain = ipa_client_install.configure_nisdomain
|
||||
else:
|
||||
|
||||
@@ -96,13 +96,13 @@ RETURN = '''
|
||||
'''
|
||||
|
||||
import os
|
||||
import inspect
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_replica import (
|
||||
AnsibleModuleLog, setup_logging, installer, DN, paths,
|
||||
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
|
||||
gen_ReplicaConfig, gen_remote_api, redirect_stdout, custodiainstance
|
||||
gen_ReplicaConfig, gen_remote_api, redirect_stdout, custodiainstance,
|
||||
getargspec
|
||||
)
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ def main():
|
||||
ansible_log.debug("-- CUSTODIA IMPORT DM PASSWORD --")
|
||||
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(custodia.import_dm_password)
|
||||
argspec = getargspec(custodia.import_dm_password)
|
||||
# pylint: enable=deprecated-method
|
||||
if "master_host_name" in argspec.args:
|
||||
custodia.import_dm_password(config.master_host_name)
|
||||
|
||||
@@ -182,6 +182,9 @@ options:
|
||||
skip_conncheck:
|
||||
description: Skip connection check to remote master
|
||||
required: yes
|
||||
sid_generation_always:
|
||||
description: Enable SID generation always
|
||||
required: yes
|
||||
author:
|
||||
- Thomas Woerner
|
||||
'''
|
||||
@@ -275,6 +278,8 @@ def main():
|
||||
# additional
|
||||
server=dict(required=True),
|
||||
skip_conncheck=dict(required=False, type='bool'),
|
||||
sid_generation_always=dict(required=False, type='bool',
|
||||
default=False),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
@@ -350,6 +355,13 @@ def main():
|
||||
# '_hostname_overridden')
|
||||
options.server = ansible_module.params.get('server')
|
||||
options.skip_conncheck = ansible_module.params.get('skip_conncheck')
|
||||
sid_generation_always = ansible_module.params.get('sid_generation_always')
|
||||
|
||||
# random serial numbers are master_only, therefore setting to False
|
||||
options.random_serial_numbers = False
|
||||
# options._random_serial_numbers is generated by ca.install_check and
|
||||
# later used by ca.install in the _setup_ca module.
|
||||
options._random_serial_numbers = False
|
||||
|
||||
# init #
|
||||
|
||||
@@ -755,7 +767,7 @@ def main():
|
||||
|
||||
ansible_log.debug("-- CHECK ADTRUST --")
|
||||
|
||||
if options.setup_adtrust:
|
||||
if options.setup_adtrust or sid_generation_always:
|
||||
adtrust.install_check(False, options, remote_api)
|
||||
|
||||
except errors.ACIError:
|
||||
@@ -838,6 +850,7 @@ def main():
|
||||
_http_ca_cert=http_ca_cert,
|
||||
_pkinit_pkcs12_info=pkinit_pkcs12_info,
|
||||
_pkinit_ca_cert=pkinit_ca_cert,
|
||||
_random_serial_numbers=options._random_serial_numbers,
|
||||
no_dnssec_validation=options.no_dnssec_validation,
|
||||
config_setup_ca=config.setup_ca,
|
||||
config_master_host_name=config.master_host_name,
|
||||
|
||||
@@ -71,6 +71,9 @@ options:
|
||||
setup_ca:
|
||||
description: Configure a dogtag CA
|
||||
required: no
|
||||
setup_adtrust:
|
||||
description: Configure AD trust capability
|
||||
required: yes
|
||||
config_master_host_name:
|
||||
description: The config master_host_name setting
|
||||
required: no
|
||||
@@ -112,6 +115,7 @@ def main():
|
||||
ccache=dict(required=True),
|
||||
_top_dir=dict(required=True),
|
||||
setup_ca=dict(required=True, type='bool'),
|
||||
setup_adtrust=dict(required=True, type='bool'),
|
||||
config_master_host_name=dict(required=True),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
@@ -140,6 +144,7 @@ def main():
|
||||
os.environ['KRB5CCNAME'] = ccache
|
||||
options._top_dir = ansible_module.params.get('_top_dir')
|
||||
options.setup_ca = ansible_module.params.get('setup_ca')
|
||||
options.setup_adtrust = ansible_module.params.get('setup_adtrust')
|
||||
config_master_host_name = ansible_module.params.get(
|
||||
'config_master_host_name')
|
||||
adtrust.netbios_name = ansible_module.params.get('adtrust_netbios_name')
|
||||
|
||||
@@ -85,6 +85,9 @@ options:
|
||||
_subject_base:
|
||||
description: The installer _subject_base setting
|
||||
required: no
|
||||
_random_serial_numbers:
|
||||
description: The installer _random_serial_numbers setting
|
||||
required: yes
|
||||
dirman_password:
|
||||
description: Directory Manager (master) password
|
||||
required: no
|
||||
@@ -144,6 +147,7 @@ def main():
|
||||
_top_dir=dict(required=True),
|
||||
_ca_subject=dict(required=True),
|
||||
_subject_base=dict(required=True),
|
||||
_random_serial_numbers=dict(required=True, type='bool'),
|
||||
dirman_password=dict(required=True, no_log=True),
|
||||
config_setup_ca=dict(required=True, type='bool'),
|
||||
config_master_host_name=dict(required=True),
|
||||
@@ -190,6 +194,8 @@ def main():
|
||||
options._subject_base = ansible_module.params.get('_subject_base')
|
||||
if options._subject_base is not None:
|
||||
options._subject_base = DN(options._subject_base)
|
||||
options._random_serial_numbers = ansible_module.params.get(
|
||||
'_random_serial_numbers')
|
||||
dirman_password = ansible_module.params.get('dirman_password')
|
||||
config_setup_ca = ansible_module.params.get('config_setup_ca')
|
||||
config_master_host_name = ansible_module.params.get(
|
||||
|
||||
@@ -149,7 +149,6 @@ RETURN = '''
|
||||
'''
|
||||
|
||||
import os
|
||||
import inspect
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_replica import (
|
||||
@@ -157,7 +156,8 @@ from ansible.module_utils.ansible_ipa_replica import (
|
||||
ansible_module_get_parsed_ip_addresses,
|
||||
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
|
||||
gen_ReplicaConfig, gen_remote_api, redirect_stdout, ipaldap,
|
||||
install_replica_ds, install_dns_records, ntpinstance, ScriptError
|
||||
install_replica_ds, install_dns_records, ntpinstance, ScriptError,
|
||||
getargspec
|
||||
)
|
||||
|
||||
|
||||
@@ -317,7 +317,7 @@ def main():
|
||||
# Configure dirsrv
|
||||
with redirect_stdout(ansible_log):
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(install_replica_ds)
|
||||
argspec = getargspec(install_replica_ds)
|
||||
# pylint: enable=deprecated-method
|
||||
if "promote" in argspec.args:
|
||||
ds = install_replica_ds(config, options, ca_enabled,
|
||||
@@ -343,7 +343,7 @@ def main():
|
||||
# pylint: enable=deprecated-method
|
||||
# Always try to install DNS records
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(install_dns_records)
|
||||
argspec = getargspec(install_dns_records)
|
||||
# pylint: enable=deprecated-method
|
||||
if "fstore" not in argspec.args:
|
||||
install_dns_records(config, options, remote_api)
|
||||
|
||||
@@ -90,14 +90,13 @@ RETURN = '''
|
||||
'''
|
||||
|
||||
import os
|
||||
import inspect
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_replica import (
|
||||
AnsibleModuleLog, setup_logging, installer, DN, paths, sysrestore,
|
||||
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
|
||||
gen_ReplicaConfig, gen_remote_api, api, redirect_stdout, create_ipa_conf,
|
||||
install_http
|
||||
install_http, getargspec
|
||||
)
|
||||
|
||||
|
||||
@@ -203,7 +202,7 @@ def main():
|
||||
master=config.master_host_name)
|
||||
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(install_http)
|
||||
argspec = getargspec(install_http)
|
||||
# pylint: enable=deprecated-method
|
||||
if "promote" in argspec.args:
|
||||
install_http(
|
||||
|
||||
@@ -78,13 +78,12 @@ RETURN = '''
|
||||
'''
|
||||
|
||||
import os
|
||||
import inspect
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_replica import (
|
||||
AnsibleModuleLog, setup_logging, installer, DN, paths, sysrestore,
|
||||
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
|
||||
gen_ReplicaConfig, api, redirect_stdout, install_krb
|
||||
gen_ReplicaConfig, api, redirect_stdout, install_krb, getargspec
|
||||
)
|
||||
|
||||
|
||||
@@ -162,7 +161,7 @@ def main():
|
||||
|
||||
with redirect_stdout(ansible_log):
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(install_krb)
|
||||
argspec = getargspec(install_krb)
|
||||
# pylint: enable=deprecated-method
|
||||
if "promote" in argspec.args:
|
||||
install_krb(
|
||||
|
||||
@@ -136,7 +136,6 @@ RETURN = '''
|
||||
'''
|
||||
|
||||
import os
|
||||
import inspect
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_replica import (
|
||||
@@ -144,7 +143,7 @@ from ansible.module_utils.ansible_ipa_replica import (
|
||||
ansible_module_get_parsed_ip_addresses, service,
|
||||
redirect_stdout, create_ipa_conf, ipautil,
|
||||
x509, validate_domain_name, common_check,
|
||||
IPA_PYTHON_VERSION
|
||||
IPA_PYTHON_VERSION, getargspec, adtrustinstance
|
||||
)
|
||||
|
||||
|
||||
@@ -271,6 +270,14 @@ def main():
|
||||
# # options.setup_adtrust = False
|
||||
# # ansible_module.warn(msg="adtrust is not supported, disabling")
|
||||
|
||||
sid_generation_always = False
|
||||
if not options.setup_adtrust:
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = getargspec(adtrustinstance.ADTRUSTInstance.__init__)
|
||||
# pylint: enable=deprecated-method
|
||||
if "fulltrust" in argspec.args:
|
||||
sid_generation_always = True
|
||||
|
||||
# if options.setup_kra and not kra_imported:
|
||||
# # if "kra" not in options._allow_missing:
|
||||
# ansible_module.fail_json(msg="kra can not be imported")
|
||||
@@ -287,7 +294,7 @@ def main():
|
||||
# create_ipa_conf has the additional master argument.
|
||||
change_master_for_certmonger = False
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(create_ipa_conf)
|
||||
argspec = getargspec(create_ipa_conf)
|
||||
# pylint: enable=deprecated-method
|
||||
if "master" in argspec.args:
|
||||
change_master_for_certmonger = True
|
||||
@@ -421,7 +428,7 @@ def main():
|
||||
try:
|
||||
with redirect_stdout(ansible_log):
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(common_check)
|
||||
argspec = getargspec(common_check)
|
||||
# pylint: enable=deprecated-method
|
||||
if "skip_mem_check" in argspec.args:
|
||||
common_check(options.no_ntp, options.skip_mem_check,
|
||||
@@ -472,6 +479,7 @@ def main():
|
||||
# additional
|
||||
client_enrolled=client_enrolled,
|
||||
change_master_for_certmonger=change_master_for_certmonger,
|
||||
sid_generation_always=sid_generation_always
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ __all__ = ["contextlib", "dnsexception", "dnsresolver", "dnsreversename",
|
||||
"common_check", "current_domain_level",
|
||||
"check_domain_level_is_supported", "promotion_check_ipa_domain",
|
||||
"SSSDConfig", "CalledProcessError", "timeconf", "ntpinstance",
|
||||
"dnsname", "kernel_keyring", "krbinstance"]
|
||||
"dnsname", "kernel_keyring", "krbinstance", "getargspec",
|
||||
"adtrustinstance"]
|
||||
|
||||
import sys
|
||||
|
||||
@@ -59,6 +60,28 @@ else:
|
||||
import logging
|
||||
from contextlib import contextmanager as contextlib_contextmanager
|
||||
|
||||
# Import getargspec from inspect or provide own getargspec for
|
||||
# Python 2 compatibility with Python 3.11+.
|
||||
try:
|
||||
from inspect import getargspec
|
||||
except ImportError:
|
||||
from collections import namedtuple
|
||||
from inspect import getfullargspec
|
||||
|
||||
# The code is copied from Python 3.10 inspect.py
|
||||
# Authors: Ka-Ping Yee <ping@lfw.org>
|
||||
# Yury Selivanov <yselivanov@sprymix.com>
|
||||
ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults')
|
||||
|
||||
def getargspec(func):
|
||||
args, varargs, varkw, defaults, kwonlyargs, _kwonlydefaults, \
|
||||
ann = getfullargspec(func)
|
||||
if kwonlyargs or ann:
|
||||
raise ValueError(
|
||||
"Function has keyword-only parameters or annotations"
|
||||
", use inspect.signature() API which can support them")
|
||||
return ArgSpec(args, varargs, varkw, defaults)
|
||||
|
||||
from ipapython.version import NUM_VERSION, VERSION
|
||||
|
||||
if NUM_VERSION < 30201:
|
||||
@@ -105,6 +128,7 @@ else:
|
||||
adtrust, bindinstance, ca, certs, dns, dsinstance, httpinstance,
|
||||
installutils, kra, krbinstance,
|
||||
otpdinstance, custodiainstance, service, upgradeinstance)
|
||||
from ipaserver.install import adtrustinstance
|
||||
try:
|
||||
from ipaserver.masters import (
|
||||
find_providing_servers, find_providing_server)
|
||||
|
||||
@@ -557,6 +557,7 @@
|
||||
_subject_base: "{{ result_ipareplica_prepare._subject_base }}"
|
||||
_pkinit_pkcs12_info: "{{ result_ipareplica_prepare._pkinit_pkcs12_info if result_ipareplica_prepare._pkinit_pkcs12_info != None else omit }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
_random_serial_numbers: "{{ result_ipareplica_prepare._random_serial_numbers }}"
|
||||
dirman_password: "{{ ipareplica_dirman_password }}"
|
||||
config_setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
|
||||
config_master_host_name:
|
||||
@@ -748,13 +749,15 @@
|
||||
ccache: "{{ result_ipareplica_prepare.ccache }}"
|
||||
_top_dir: "{{ result_ipareplica_prepare._top_dir }}"
|
||||
setup_ca: "{{ result_ipareplica_prepare.config_setup_ca }}"
|
||||
setup_adtrust: "{{ result_ipareplica_test.setup_adtrust }}"
|
||||
config_master_host_name:
|
||||
"{{ result_ipareplica_prepare.config_master_host_name }}"
|
||||
adtrust_netbios_name:
|
||||
"{{ result_ipareplica_prepare.adtrust_netbios_name }}"
|
||||
adtrust_reset_netbios_name:
|
||||
"{{ result_ipareplica_prepare.adtrust_reset_netbios_name }}"
|
||||
when: result_ipareplica_test.setup_adtrust
|
||||
when: result_ipareplica_test.setup_adtrust or
|
||||
result_ipareplica_test.sid_generation_always
|
||||
|
||||
- name: Install - Enable IPA
|
||||
ipareplica_enable_ipa:
|
||||
|
||||
@@ -141,6 +141,9 @@ options:
|
||||
setup_ca:
|
||||
description: Configure a dogtag CA
|
||||
required: yes
|
||||
sid_generation_always:
|
||||
description: Enable SID generation always
|
||||
required: yes
|
||||
_hostname_overridden:
|
||||
description: The installer _hostname_overridden setting
|
||||
required: yes
|
||||
@@ -213,6 +216,10 @@ def main():
|
||||
|
||||
# additional
|
||||
setup_ca=dict(required=False, type='bool', default=False),
|
||||
random_serial_numbers=dict(required=False, type='bool',
|
||||
default=False),
|
||||
sid_generation_always=dict(required=False, type='bool',
|
||||
default=False),
|
||||
_hostname_overridden=dict(required=False, type='bool',
|
||||
default=False),
|
||||
),
|
||||
@@ -225,9 +232,11 @@ def main():
|
||||
|
||||
# initialize return values for flake ############################
|
||||
|
||||
# These are set by ca.install_check
|
||||
# These are set by ca.install_check and need to be passed to ca.install
|
||||
# in the _setup_ca module and also some others.
|
||||
options._subject_base = None
|
||||
options._ca_subject = None
|
||||
options._random_serial_numbers = False
|
||||
|
||||
# set values ####################################################
|
||||
|
||||
@@ -277,8 +286,11 @@ def main():
|
||||
options.netbios_name = ansible_module.params.get('netbios_name')
|
||||
# additional
|
||||
options.setup_ca = ansible_module.params.get('setup_ca')
|
||||
options.random_serial_numbers = ansible_module.params.get(
|
||||
'random_serial_numbers')
|
||||
options._host_name_overridden = ansible_module.params.get(
|
||||
'_hostname_overridden')
|
||||
sid_generation_always = ansible_module.params.get('sid_generation_always')
|
||||
options.kasp_db_file = None
|
||||
|
||||
# init ##################################################################
|
||||
@@ -371,7 +383,7 @@ def main():
|
||||
logger.debug('Starting Directory Server')
|
||||
services.knownservices.dirsrv.start(instance_name)
|
||||
|
||||
if options.setup_adtrust:
|
||||
if options.setup_adtrust or sid_generation_always:
|
||||
with redirect_stdout(ansible_log):
|
||||
adtrust.install_check(False, options, api)
|
||||
|
||||
@@ -405,6 +417,7 @@ def main():
|
||||
_subject_base=options._subject_base,
|
||||
ca_subject=options.ca_subject,
|
||||
_ca_subject=options._ca_subject,
|
||||
_random_serial_numbers=options._random_serial_numbers,
|
||||
# dns
|
||||
reverse_zones=options.reverse_zones,
|
||||
forward_policy=options.forward_policy,
|
||||
|
||||
@@ -132,6 +132,9 @@ options:
|
||||
ca_signing_algorithm:
|
||||
description: Signing algorithm of the IPA CA certificate
|
||||
required: yes
|
||||
_random_serial_numbers:
|
||||
description: The installer _random_serial_numbers setting
|
||||
required: yes
|
||||
reverse_zones:
|
||||
description: The reverse DNS zones to use
|
||||
required: yes
|
||||
@@ -204,6 +207,7 @@ def main():
|
||||
ca_subject=dict(required=False),
|
||||
_ca_subject=dict(required=False),
|
||||
ca_signing_algorithm=dict(required=False),
|
||||
_random_serial_numbers=dict(required=True, type='bool'),
|
||||
# dns
|
||||
reverse_zones=dict(required=False, type='list', default=[]),
|
||||
no_reverse=dict(required=False, type='bool', default=False),
|
||||
@@ -259,6 +263,8 @@ def main():
|
||||
options._ca_subject = ansible_module.params.get('_ca_subject')
|
||||
options.ca_signing_algorithm = ansible_module.params.get(
|
||||
'ca_signing_algorithm')
|
||||
options._random_serial_numbers = ansible_module.params.get(
|
||||
'_random_serial_numbers')
|
||||
# dns
|
||||
options.reverse_zones = ansible_module.params.get('reverse_zones')
|
||||
options.no_reverse = ansible_module.params.get('no_reverse')
|
||||
|
||||
@@ -53,12 +53,11 @@ EXAMPLES = '''
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
import inspect
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_server import (
|
||||
AnsibleModuleLog, setup_logging, options, sysrestore, paths,
|
||||
redirect_stdout, time_service, sync_time, ntpinstance, timeconf
|
||||
redirect_stdout, time_service, sync_time, ntpinstance, timeconf,
|
||||
getargspec
|
||||
)
|
||||
|
||||
|
||||
@@ -94,7 +93,7 @@ def main():
|
||||
ansible_module.log("Synchronizing time")
|
||||
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(sync_time)
|
||||
argspec = getargspec(sync_time)
|
||||
# pylint: enable=deprecated-method
|
||||
if "options" not in argspec.args:
|
||||
synced_ntp = sync_time(options.ntp_servers, options.ntp_pool,
|
||||
|
||||
@@ -212,7 +212,6 @@ RETURN = '''
|
||||
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import random
|
||||
from shutil import copyfile
|
||||
|
||||
@@ -226,7 +225,7 @@ from ansible.module_utils.ansible_ipa_server import (
|
||||
read_cache, ca, tasks, check_ldap_conf, timeconf, httpinstance,
|
||||
check_dirsrv, ScriptError, get_fqdn, verify_fqdn, BadHostError,
|
||||
validate_domain_name, load_pkcs12, IPA_PYTHON_VERSION,
|
||||
encode_certificate, check_available_memory
|
||||
encode_certificate, check_available_memory, getargspec, adtrustinstance
|
||||
)
|
||||
from ansible.module_utils import six
|
||||
|
||||
@@ -395,12 +394,16 @@ def main():
|
||||
|
||||
# version specific ######################################################
|
||||
|
||||
if options.setup_adtrust and not adtrust_imported:
|
||||
# if "adtrust" not in options._allow_missing:
|
||||
ansible_module.fail_json(msg="adtrust can not be imported")
|
||||
# else:
|
||||
# options.setup_adtrust = False
|
||||
# ansible_module.warn(msg="adtrust is not supported, disabling")
|
||||
sid_generation_always = False
|
||||
if not options.setup_adtrust:
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = getargspec(adtrustinstance.ADTRUSTInstance.__init__)
|
||||
# pylint: enable=deprecated-method
|
||||
if "fulltrust" in argspec.args:
|
||||
sid_generation_always = True
|
||||
else:
|
||||
if not adtrust_imported:
|
||||
ansible_module.fail_json(msg="adtrust can not be imported")
|
||||
|
||||
if options.setup_kra and not kra_imported:
|
||||
# if "kra" not in options._allow_missing:
|
||||
@@ -522,7 +525,8 @@ def main():
|
||||
"You cannot specify an --enable-compat option without the "
|
||||
"--setup-adtrust option")
|
||||
|
||||
if self.netbios_name:
|
||||
# Deactivate test for new IPA SID generation
|
||||
if self.netbios_name and not sid_generation_always:
|
||||
raise RuntimeError(
|
||||
"You cannot specify a --netbios-name option without the "
|
||||
"--setup-adtrust option")
|
||||
@@ -944,7 +948,7 @@ def main():
|
||||
realm_name = options.realm_name.upper()
|
||||
|
||||
# pylint: disable=deprecated-method
|
||||
argspec = inspect.getargspec(validate_domain_name)
|
||||
argspec = getargspec(validate_domain_name)
|
||||
# pylint: enable=deprecated-method
|
||||
if "entity" in argspec.args:
|
||||
# NUM_VERSION >= 40690:
|
||||
@@ -1079,7 +1083,8 @@ def main():
|
||||
ntp_pool=options.ntp_pool,
|
||||
# additional
|
||||
_installation_cleanup=_installation_cleanup,
|
||||
domainlevel=options.domainlevel)
|
||||
domainlevel=options.domainlevel,
|
||||
sid_generation_always=sid_generation_always)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -41,7 +41,7 @@ __all__ = ["IPAChangeConf", "certmonger", "sysrestore", "root_logger",
|
||||
"adtrustinstance", "IPAAPI_USER", "sync_time", "PKIIniLoader",
|
||||
"default_subject_base", "default_ca_subject_dn",
|
||||
"check_ldap_conf", "encode_certificate", "decode_certificate",
|
||||
"check_available_memory"]
|
||||
"check_available_memory", "getargspec"]
|
||||
|
||||
import sys
|
||||
|
||||
@@ -58,6 +58,28 @@ else:
|
||||
from ansible.module_utils import six
|
||||
import base64
|
||||
|
||||
# Import getargspec from inspect or provide own getargspec for
|
||||
# Python 2 compatibility with Python 3.11+.
|
||||
try:
|
||||
from inspect import getargspec
|
||||
except ImportError:
|
||||
from collections import namedtuple
|
||||
from inspect import getfullargspec
|
||||
|
||||
# The code is copied from Python 3.10 inspect.py
|
||||
# Authors: Ka-Ping Yee <ping@lfw.org>
|
||||
# Yury Selivanov <yselivanov@sprymix.com>
|
||||
ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults')
|
||||
|
||||
def getargspec(func):
|
||||
args, varargs, varkw, defaults, kwonlyargs, _kwonlydefaults, \
|
||||
ann = getfullargspec(func)
|
||||
if kwonlyargs or ann:
|
||||
raise ValueError(
|
||||
"Function has keyword-only parameters or annotations"
|
||||
", use inspect.signature() API which can support them")
|
||||
return ArgSpec(args, varargs, varkw, defaults)
|
||||
|
||||
from ipapython.version import NUM_VERSION, VERSION
|
||||
|
||||
if NUM_VERSION < 30201:
|
||||
|
||||
@@ -11,4 +11,4 @@
|
||||
force: yes
|
||||
- name: Install - Extend ipaserver_external_cert_files with "/root/{{ item | basename }}"
|
||||
set_fact:
|
||||
ipaserver_external_cert_files: "{{ ipaserver_external_cert_files }} + [ '/root/{{ item | basename }}' ]"
|
||||
ipaserver_external_cert_files: "{{ ipaserver_external_cert_files + [ '/root/' + (item | basename) ] }}"
|
||||
|
||||
@@ -191,6 +191,8 @@
|
||||
secondary_rid_base: "{{ ipaserver_secondary_rid_base | default(omit) }}"
|
||||
### additional ###
|
||||
setup_ca: "{{ result_ipaserver_test.setup_ca }}"
|
||||
sid_generation_always: "{{ result_ipaserver_test.sid_generation_always }}"
|
||||
random_serial_numbers: no
|
||||
_hostname_overridden: "{{ result_ipaserver_test._hostname_overridden }}"
|
||||
register: result_ipaserver_prepare
|
||||
|
||||
@@ -298,6 +300,7 @@
|
||||
_ca_subject: "{{ result_ipaserver_prepare._ca_subject }}"
|
||||
ca_signing_algorithm: "{{ ipaserver_ca_signing_algorithm |
|
||||
default(omit) }}"
|
||||
_random_serial_numbers: "{{ result_ipaserver_prepare._random_serial_numbers }}"
|
||||
reverse_zones: "{{ result_ipaserver_prepare.reverse_zones }}"
|
||||
no_reverse: "{{ ipaserver_no_reverse }}"
|
||||
auto_forwarders: "{{ ipaserver_auto_forwarders }}"
|
||||
@@ -392,7 +395,8 @@
|
||||
adtrust_netbios_name: "{{ result_ipaserver_prepare.adtrust_netbios_name }}"
|
||||
adtrust_reset_netbios_name:
|
||||
"{{ result_ipaserver_prepare.adtrust_reset_netbios_name }}"
|
||||
when: result_ipaserver_test.setup_adtrust
|
||||
when: result_ipaserver_test.setup_adtrust or
|
||||
result_ipaserver_test.sid_generation_always
|
||||
|
||||
- name: Install - Set DS password
|
||||
ipaserver_set_ds_password:
|
||||
|
||||
111
roles/ipasmartcard_client/README.md
Normal file
111
roles/ipasmartcard_client/README.md
Normal file
@@ -0,0 +1,111 @@
|
||||
ipasmartcard_client role
|
||||
========================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This role allows to configure IPA clients for Smart Card authentication.
|
||||
|
||||
**Note**: The ansible-freeipa smartcard client role requires an enrolled IPA client.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
* Client setup for Smart Card authentication
|
||||
|
||||
|
||||
Supported FreeIPA Versions
|
||||
--------------------------
|
||||
|
||||
FreeIPA versions 4.5 and up are supported by this role.
|
||||
|
||||
|
||||
Supported Distributions
|
||||
-----------------------
|
||||
|
||||
* RHEL/CentOS 7.6+
|
||||
* Fedora 26+
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
* Supported distribution (needed for package installation only, see above)
|
||||
* Enrolled IPA client
|
||||
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
Only the enablement of smartcards is supported by the role, there is no disablement.
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Example inventory file with IPA clients:
|
||||
|
||||
```ini
|
||||
[ipaclients]
|
||||
ipaclient1.example.com
|
||||
ipaclient2.example.com
|
||||
|
||||
[ipaclients:vars]
|
||||
ipaadmin_password=SomeADMINpassword
|
||||
ipasmartcard_client_ca_certs=/etc/ipa/ca.crt
|
||||
```
|
||||
|
||||
Example playbook to setup smartcard for the IPA clients using admin password and ipasmartcard_client_ca_certs from inventory file:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to setup smartcard for IPA clients
|
||||
hosts: ipaclients
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- role: ipasmartcard_client
|
||||
state: present
|
||||
```
|
||||
|
||||
Playbooks
|
||||
=========
|
||||
|
||||
The playbooks needed to setup smartcard for the IPA clients is part of the repository in the playbooks folder.
|
||||
|
||||
```
|
||||
install-smartcard-clients.yml
|
||||
```
|
||||
|
||||
Please remember to link or copy the playbooks to the base directory of ansible-freeipa if you want to use the roles within the source archive.
|
||||
|
||||
|
||||
How to setup smartcard for clients
|
||||
----------------------------------
|
||||
|
||||
```bash
|
||||
ansible-playbook -v -i inventory/hosts install-smartcard-clients.yml
|
||||
```
|
||||
This will setup the clients for smartcard use.
|
||||
|
||||
|
||||
Variables
|
||||
=========
|
||||
|
||||
Variable | Description | Required
|
||||
-------- | ----------- | --------
|
||||
`ipaadmin_principal` | The kerberos principal used for admin. Will be set to `admin` if not set. (string) | no
|
||||
`ipaadmin_password` | The password for the IPA admin user. As an alternative an admin user keytab can be used instead with `ipaadmin_keytab`. (string) | yes
|
||||
`ipaadmin_keytab` | The admin keytab as an alternative to `ipaadmin_password`. (string) | no
|
||||
`ipasmartcard_client_ca_certs` | The CA certificates for smartcard use. If `ipasmartcard_client_ca_certs` is not set, but `ipasmartcard_server_ca_certs`, then `ipasmartcard_server_ca_certs` will be used. | yes
|
||||
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Thomas Woerner
|
||||
4
roles/ipasmartcard_client/defaults/main.yml
Normal file
4
roles/ipasmartcard_client/defaults/main.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
# defaults file for ipasmartcard_client role
|
||||
|
||||
ipaclient_install_packages: yes
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2022 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
cert_file=$1
|
||||
db=$2
|
||||
|
||||
if [ -z "${cert_file}" ] || [ -z "${db}" ]; then
|
||||
echo "Usage: $0 <ca cert> <db file>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat "${cert_file}" >> "${db}"
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2022 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
cert_file=$1
|
||||
db=$2
|
||||
|
||||
if [ -z "${cert_file}" ] || [ -z "${db}" ]; then
|
||||
echo "Usage: $0 <ca cert> <db file>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
uuid=$(uuidgen)
|
||||
certutil -d "${db}" -A -i "${cert_file}" -n "Smart Card CA ${uuid}" -t CT,C,C
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2022 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
nssdb=$1
|
||||
module_name="OpenSC"
|
||||
pkcs11_shared_lib="/usr/lib64/opensc-pkcs11.so"
|
||||
|
||||
if [ -z "${nssdb}" ]; then
|
||||
echo "Usage: $0 <nssdb>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if modutil -dbdir "${nssdb}" -list | grep -q "${module_name}" || p11-kit list-modules | grep -i "${module_name}" -q
|
||||
then
|
||||
echo "${module_name} PKCS#11 module already configured"
|
||||
else
|
||||
echo "" | modutil -dbdir "${nssdb}" -add "${module_name}" -libfile "${pkcs11_shared_lib}"
|
||||
fi
|
||||
@@ -0,0 +1,83 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2022 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.0',
|
||||
'supported_by': 'community',
|
||||
'status': ['preview'],
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ipasmartcard_client_get_vars
|
||||
short description:
|
||||
Get variables from ipaplatform and python interpreter used for the module.
|
||||
description:
|
||||
Get variables from ipaplatform and python interpreter used for the module.
|
||||
options:
|
||||
author:
|
||||
- Thomas Woerner
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Get VARS from IPA
|
||||
ipasmartcard_client_get_vars:
|
||||
register: ipasmartcard_client_vars
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
NSS_DB_DIR:
|
||||
description: paths.NSS_DB_DIR from ipaplatform
|
||||
returned: always
|
||||
type: str
|
||||
USE_AUTHSELECT:
|
||||
description: True if "AUTHSELECT" is defined in paths
|
||||
returned: always
|
||||
type: bool
|
||||
python_interpreter:
|
||||
description: Python interpreter from sys.executable
|
||||
returned: always
|
||||
type: str
|
||||
'''
|
||||
|
||||
import sys
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ipaplatform.paths import paths
|
||||
|
||||
|
||||
def main():
|
||||
ansible_module = AnsibleModule(
|
||||
argument_spec={},
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
ansible_module.exit_json(changed=False,
|
||||
NSS_DB_DIR=paths.NSS_DB_DIR,
|
||||
USE_AUTHSELECT=hasattr(paths, "AUTHSELECT"),
|
||||
python_interpreter=sys.executable)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,110 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Based on ipa-replica-install code
|
||||
#
|
||||
# Copyright (C) 2022 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.0',
|
||||
'supported_by': 'community',
|
||||
'status': ['preview'],
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ipasmartcard_server_validate_ca_certs
|
||||
short description: Validate CA certs
|
||||
description: Validate CA certs
|
||||
options:
|
||||
ca_cert_files:
|
||||
description:
|
||||
List of files containing CA certificates for the service certificate
|
||||
files
|
||||
required: yes
|
||||
author:
|
||||
- Thomas Woerner
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
import os.path
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
try:
|
||||
from ipalib import x509
|
||||
except ImportError:
|
||||
x509 = None
|
||||
|
||||
|
||||
def main():
|
||||
ansible_module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
ca_cert_files=dict(required=False, type='list', default=[]),
|
||||
),
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
# get parameters #
|
||||
|
||||
ca_cert_files = ansible_module.params.get('ca_cert_files')
|
||||
|
||||
# import check #
|
||||
|
||||
if x509 is None:
|
||||
ansible_module.fail_json(msg="Failed to import x509 from ipalib")
|
||||
|
||||
# validate ca certs #
|
||||
|
||||
if ca_cert_files is not None:
|
||||
if not isinstance(ca_cert_files, list):
|
||||
ansible_module.fail_json(
|
||||
msg="Expected list, got %s" % repr(ca_cert_files))
|
||||
# remove duplicates
|
||||
ca_cert_files = list(dict.fromkeys(ca_cert_files))
|
||||
# validate
|
||||
for cert in ca_cert_files:
|
||||
if not os.path.exists(cert):
|
||||
ansible_module.fail_json(msg="'%s' does not exist" % cert)
|
||||
if not os.path.isfile(cert):
|
||||
ansible_module.fail_json(msg="'%s' is not a file" % cert)
|
||||
if not os.path.isabs(cert):
|
||||
ansible_module.fail_json(
|
||||
msg="'%s' is not an absolute file path" % cert)
|
||||
try:
|
||||
x509.load_certificate_from_file(cert)
|
||||
except Exception:
|
||||
ansible_module.fail_json(
|
||||
msg="'%s' is not a valid certificate file" % cert)
|
||||
|
||||
# exit #
|
||||
|
||||
ansible_module.exit_json(changed=False,
|
||||
ca_cert_files=ca_cert_files)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
22
roles/ipasmartcard_client/meta/main.yml
Normal file
22
roles/ipasmartcard_client/meta/main.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
dependencies: []
|
||||
|
||||
galaxy_info:
|
||||
author: Thomas Woerner
|
||||
description: A role to setup IPA server(s) for Smart Card authentication
|
||||
company: Red Hat, Inc
|
||||
license: GPLv3
|
||||
min_ansible_version: 2.8
|
||||
platforms:
|
||||
- name: Fedora
|
||||
versions:
|
||||
- all
|
||||
- name: EL
|
||||
versions:
|
||||
- 7
|
||||
- 8
|
||||
galaxy_tags:
|
||||
- identity
|
||||
- ipa
|
||||
- freeipa
|
||||
- smartcard
|
||||
173
roles/ipasmartcard_client/tasks/main.yml
Normal file
173
roles/ipasmartcard_client/tasks/main.yml
Normal file
@@ -0,0 +1,173 @@
|
||||
---
|
||||
# tasks file for ipasmartcard_client role
|
||||
|
||||
- name: Uninstall smartcard client
|
||||
ansible.builtin.fail: msg="Uninstalling smartcard for IPA is not supported"
|
||||
when: state|default('present') == 'absent'
|
||||
|
||||
- name: Import variables specific to distribution
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "vars/{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_version'] }}.yml"
|
||||
- "vars/{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_major_version'] }}.yml"
|
||||
- "vars/{{ ansible_facts['distribution'] }}.yml"
|
||||
# os_family is used as a fallback for distros which are not currently
|
||||
# supported, but are based on a supported distro family. For example,
|
||||
# Oracle, Rocky, Alma and Alibaba linux, which are all "RedHat" based.
|
||||
- "vars/{{ ansible_facts['os_family'] }}-{{ ansible_facts['distribution_version'] }}.yml"
|
||||
- "vars/{{ ansible_facts['os_family'] }}-{{ ansible_facts['distribution_major_version'] }}.yml"
|
||||
- "vars/{{ ansible_facts['os_family'] }}.yml"
|
||||
# If neither distro nor family is supported, try a default configuration.
|
||||
- "vars/default.yml"
|
||||
|
||||
- block:
|
||||
|
||||
# CA CERTS
|
||||
|
||||
# Use "ipasmartcard_server_ca_certs"
|
||||
|
||||
- name: Use "ipasmartcard_server_ca_certs"
|
||||
ansible.builtin.set_fact:
|
||||
ipasmartcard_client_ca_certs: "{{ ipasmartcard_server_ca_certs }}"
|
||||
when: ipasmartcard_client_ca_certs is not defined and
|
||||
ipasmartcard_server_ca_certs is defined
|
||||
|
||||
# Fail on empty "ipasmartcard_client_ca_certs"
|
||||
|
||||
- name: Fail on empty "ipasmartcard_client_ca_certs"
|
||||
ansible.builtin.fail: msg="No CA certs given in 'ipasmartcard_client_ca_certs'"
|
||||
when: ipasmartcard_client_ca_certs is not defined or
|
||||
ipasmartcard_client_ca_certs | length < 1
|
||||
|
||||
# Validate ipasmartcard_client_ca_certs
|
||||
|
||||
- name: Validate CA certs "{{ ipasmartcard_client_ca_certs }}"
|
||||
ipasmartcard_client_validate_ca_certs:
|
||||
ca_cert_files: "{{ ipasmartcard_client_ca_certs }}"
|
||||
register: result_validate_ca_certs
|
||||
|
||||
# INSTALL needed packages: opensc, dconf and krb5-pkinit-openssl
|
||||
|
||||
- name: Ensure needed packages are installed
|
||||
ansible.builtin.package:
|
||||
name: "{{ ipasmartcard_client_packages }}"
|
||||
state: present
|
||||
|
||||
# REMOVE pam_pkcs11
|
||||
|
||||
- name: Ensure pam_pkcs11 is missing
|
||||
ansible.builtin.package:
|
||||
name: "{{ ipasmartcard_client_remove_pam_pkcs11_packages }}"
|
||||
state: absent
|
||||
|
||||
# KINIT
|
||||
|
||||
- name: Set default principal if not given
|
||||
ansible.builtin.set_fact:
|
||||
ipaadmin_principal: admin
|
||||
when: ipaadmin_principal is undefined
|
||||
|
||||
- name: kinit using "{{ ipaadmin_principal }}" password
|
||||
ansible.builtin.command: kinit "{{ ipaadmin_principal }}"
|
||||
args:
|
||||
stdin: "{{ ipaadmin_password }}"
|
||||
when: ipaadmin_password is defined
|
||||
|
||||
- name: kinit using "{{ ipaadmin_principal }}" keytab
|
||||
ansible.builtin.command: kinit -kt "{{ ipaadmin_keytab }}" "{{ ipaadmin_principal }}"
|
||||
when: ipaadmin_keytab is defined
|
||||
|
||||
# Enable and start smartcard daemon
|
||||
|
||||
- name: Enable and start smartcard daemon
|
||||
ansible.builtin.service:
|
||||
name: pcscd
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
# GET VARS FROM IPA
|
||||
|
||||
- name: Get VARS from IPA
|
||||
ipasmartcard_client_get_vars:
|
||||
register: ipasmartcard_client_vars
|
||||
|
||||
# Add pkcs11 module to systemwide db
|
||||
|
||||
- name: Add pkcs11 module to systemwide db
|
||||
ansible.builtin.script: ipasmartcard_client_add_pkcs11_module_to_systemwide_db.sh
|
||||
"{{ ipasmartcard_client_vars.NSS_DB_DIR }}"
|
||||
|
||||
# Ensure /etc/sssd/pki exists
|
||||
|
||||
- block:
|
||||
- name: Ensure /etc/sssd/pki exists
|
||||
ansible.builtin.file:
|
||||
path: /etc/sssd/pki
|
||||
state: directory
|
||||
mode: 0711
|
||||
|
||||
- name: Ensure /etc/sssd/pki/sssd_auth_ca_db.pem is absent
|
||||
ansible.builtin.file:
|
||||
path: /etc/sssd/pki/sssd_auth_ca_db.pem
|
||||
state: absent
|
||||
|
||||
when: ipasmartcard_client_vars.USE_AUTHSELECT
|
||||
|
||||
# Upload smartcard CA certificates to systemwide db
|
||||
|
||||
- name: Upload smartcard CA certificates to systemwide db
|
||||
ansible.builtin.script: ipasmartcard_client_add_ca_to_systemwide_db.sh
|
||||
"{{ item }}"
|
||||
"{{ ipasmartcard_client_vars.NSS_DB_DIR }}"
|
||||
with_items: "{{ result_validate_ca_certs.ca_cert_files }}"
|
||||
|
||||
# Newer version of sssd use OpenSSL and read the CA certs
|
||||
# from /etc/sssd/pki/sssd_auth_ca_db.pem
|
||||
|
||||
- name: Add CA certs to /etc/sssd/pki/sssd_auth_ca_db.pem
|
||||
ansible.builtin.script: ipasmartcard_client_add_ca_to_sssd_auth_ca_db.sh
|
||||
"{{ item }}"
|
||||
/etc/sssd/pki/sssd_auth_ca_db.pem
|
||||
with_items: "{{ result_validate_ca_certs.ca_cert_files }}"
|
||||
when: ipasmartcard_client_vars.USE_AUTHSELECT
|
||||
|
||||
# Update ipa CA certificate store
|
||||
|
||||
- name: Update ipa CA certificate store
|
||||
ansible.builtin.command: ipa-certupdate
|
||||
|
||||
# Run authselect or authconfig to configure smartcard auth
|
||||
|
||||
- name: Use authselect to enable Smart Card authentication
|
||||
ansible.builtin.command: authselect enable-feature with-smartcard
|
||||
when: ipasmartcard_client_vars.USE_AUTHSELECT
|
||||
|
||||
- name: Use authconfig to enable Smart Card authentication
|
||||
ansible.builtin.command: authconfig --enablesssd --enablesssdauth --enablesmartcard --smartcardmodule=sssd --smartcardaction=1 --updateall
|
||||
when: not ipasmartcard_client_vars.USE_AUTHSELECT
|
||||
|
||||
# Set pam_cert_auth=True in /etc/sssd/sssd.conf
|
||||
|
||||
- name: Store NSS OCSP upgrade state
|
||||
ansible.builtin.command: "{{ ipasmartcard_client_vars.python_interpreter }}"
|
||||
args:
|
||||
stdin: |
|
||||
from SSSDConfig import SSSDConfig
|
||||
c = SSSDConfig()
|
||||
c.import_config()
|
||||
c.set("pam", "pam_cert_auth", "True")
|
||||
c.write()
|
||||
when: ipasmartcard_client_vars.USE_AUTHSELECT
|
||||
|
||||
# Restart sssd
|
||||
|
||||
- name: Restart sssd
|
||||
ansible.builtin.service:
|
||||
name: sssd
|
||||
state: restarted
|
||||
|
||||
### ALWAYS ###
|
||||
|
||||
always:
|
||||
- name: kdestroy
|
||||
ansible.builtin.command: kdestroy -A
|
||||
3
roles/ipasmartcard_client/vars/default.yml
Normal file
3
roles/ipasmartcard_client/vars/default.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
ipasmartcard_client_remove_pam_pkcs11_packages: [ "pam_pkcs11" ]
|
||||
ipasmartcard_client_packages: [ "opensc", "dconf", "krb5-pkinit-openssl" ]
|
||||
169
roles/ipasmartcard_server/README.md
Normal file
169
roles/ipasmartcard_server/README.md
Normal file
@@ -0,0 +1,169 @@
|
||||
ipasmartcard_server role
|
||||
========================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This role allows to configure an IPA server (master or replica) for Smart Card authentication.
|
||||
|
||||
**Note**: The ansible-freeipa smartcard server role requires a configured IPA server with ipa-ca.DOMAIN resolvable by the DNS server.
|
||||
|
||||
With external DNS ipa-ca.DOMAIN needs to be set.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
* Server setup for Smart Card authentication
|
||||
|
||||
|
||||
Supported FreeIPA Versions
|
||||
--------------------------
|
||||
|
||||
FreeIPA versions 4.5 and up are supported by this role.
|
||||
|
||||
|
||||
Supported Distributions
|
||||
-----------------------
|
||||
|
||||
* RHEL/CentOS 7.6+
|
||||
* Fedora 26+
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
* Supported distribution (needed for package installation only, see above)
|
||||
* Deployed IPA server
|
||||
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
Only the enablement of smartcards is supported by the role, there is no disablement. The disablement of features in IPA in not supported.
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Example inventory file with ipa server and replicas:
|
||||
|
||||
```ini
|
||||
[ipaserver]
|
||||
ipaserver.example.com
|
||||
|
||||
[ipareplicas]
|
||||
ipareplica1.example.com
|
||||
ipareplica2.example.com
|
||||
|
||||
[ipacluster:children]
|
||||
ipaserver
|
||||
ipareplicas
|
||||
|
||||
[ipacluster:vars]
|
||||
ipaadmin_password=SomeADMINpassword
|
||||
ipasmartcard_server_ca_certs=/etc/ipa/ca.crt
|
||||
```
|
||||
|
||||
Example playbook to setup smartcard for the IPA server using admin password and ipasmartcard_server_ca_certs from inventory file:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to setup smartcard for IPA server
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- role: ipasmartcard_server
|
||||
state: present
|
||||
```
|
||||
|
||||
Example playbook to setup smartcard for the IPA servers in ipareplicas group using admin password and ipasmartcard_server_ca_certs from inventory file:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to setup smartcard for IPA replicas
|
||||
hosts: ipareplicas
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- role: ipasmartcard_server
|
||||
state: present
|
||||
```
|
||||
|
||||
Example playbook to setup smartcard for the IPA servers in ipaserver and ipareplicas group using admin password and ipasmartcard_server_ca_certs from inventory file:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to setup smartcard for IPA server and replicas
|
||||
hosts: ipaserver, ipareplicas
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- role: ipasmartcard_server
|
||||
state: present
|
||||
```
|
||||
|
||||
|
||||
Playbooks
|
||||
=========
|
||||
|
||||
The playbooks needed to setup smartcard for the IPA server and the replicas are part of the repository in the playbooks folder.
|
||||
|
||||
```
|
||||
install-smartcard-server.yml
|
||||
install-smartcard-servers.yml
|
||||
install-smartcard-replicas.yml
|
||||
```
|
||||
|
||||
Please remember to link or copy the playbooks to the base directory of ansible-freeipa if you want to use the roles within the source archive.
|
||||
|
||||
|
||||
How to setup smartcard for server
|
||||
---------------------------------
|
||||
|
||||
```bash
|
||||
ansible-playbook -v -i inventory/hosts install-smartcard-server.yml
|
||||
```
|
||||
This will setup the server for smartcard use.
|
||||
|
||||
|
||||
How to setup smartcard for replicas
|
||||
-----------------------------------
|
||||
|
||||
```bash
|
||||
ansible-playbook -v -i inventory/hosts install-smartcard-replicas.yml
|
||||
```
|
||||
This will setup the replicas for smartcard use.
|
||||
|
||||
|
||||
How to setup smartcard for server and replicas
|
||||
----------------------------------------------
|
||||
|
||||
```bash
|
||||
ansible-playbook -v -i inventory/hosts install-smartcard-servers.yml
|
||||
```
|
||||
This will setup the replicas for smartcard use.
|
||||
|
||||
|
||||
Variables
|
||||
=========
|
||||
|
||||
Variable | Description | Required
|
||||
-------- | ----------- | --------
|
||||
`ipaadmin_principal` | The kerberos principal used for admin. Will be set to `admin` if not set. (string) | no
|
||||
`ipaadmin_password` | The password for the IPA admin user. As an alternative an admin user keytab can be used instead with `ipaadmin_keytab`. (string) | yes
|
||||
`ipaadmin_keytab` | The admin keytab as an alternative to `ipaadmin_password`. (string) | no
|
||||
`ipaserver_hostname` | Fully qualified name of the server. By default `ansible_facts['fqdn']` will be used. (string) | no
|
||||
`ipaserver_domain` | The primary DNS domain of an existing IPA deployment. By default the domain will be used from ipa server-find result. (string) | no
|
||||
`ipasmartcard_server_ca_certs` | The CA certificates for smartcard use. (list of string) | yes
|
||||
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Thomas Woerner
|
||||
4
roles/ipasmartcard_server/defaults/main.yml
Normal file
4
roles/ipasmartcard_server/defaults/main.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
# defaults file for ipasmartcard_server role
|
||||
|
||||
ipaserver_install_packages: yes
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2022 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
cert_file=$1
|
||||
db=$2
|
||||
|
||||
if [ -z "${cert_file}" ] || [ -z "${db}" ]; then
|
||||
echo "Usage: $0 <ca cert> <db file>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat "${cert_file}" >> "${db}"
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2022 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
cert_file=$1
|
||||
db=$2
|
||||
|
||||
if [ -z "${cert_file}" ] || [ -z "${db}" ]; then
|
||||
echo "Usage: $0 <ca cert> <db file>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
uuid=$(uuidgen)
|
||||
certutil -d "${db}" -A -i "${cert_file}" -n "Smart Card CA ${uuid}" -t CT,C,C
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2022 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
directive=$1
|
||||
conf_file=$2
|
||||
|
||||
if [ -z "${directive}" ] || [ -z "${conf_file}" ]; then
|
||||
echo "Usage: $0 <directive> <config file>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -q "${directive} " "${conf_file}"
|
||||
then
|
||||
sed -i.ipabkp -r "s/^#*[[:space:]]*${directive}[[:space:]]+(on|off)$/${directive} on/" "${conf_file}"
|
||||
else
|
||||
sed -i.ipabkp "/<\/VirtualHost>/i ${directive} on" "${conf_file}"
|
||||
fi
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2022 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
directive=$1
|
||||
nss_conf=$2
|
||||
nickname=$3
|
||||
alias_dir=$4
|
||||
|
||||
if [ -z "${directive}" ] || [ -z "${nss_conf}" ] || [ -z "${nickname}" ] ||
|
||||
[ -z "${alias_dir}" ]
|
||||
then
|
||||
echo "Usage: $0 <directive> <nss conf> <nickname directive> <alias directory>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
http_cert_nick=$(grep "${nickname}" "${nss_conf}" | cut -f 2 -d ' ')
|
||||
certutil -M -n "$http_cert_nick" -d "${alias_dir}" -f "${alias_dir}/pwdfile.txt" -t "Pu,u,u"
|
||||
@@ -0,0 +1,159 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2022 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.0',
|
||||
'supported_by': 'community',
|
||||
'status': ['preview'],
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ipasmartcard_server_get_vars
|
||||
short description:
|
||||
Get variables from ipaplatform and ipaserver and python interpreter.
|
||||
description:
|
||||
Get variables from ipaplatform and ipaserver and python interpreter.
|
||||
options:
|
||||
author:
|
||||
- Thomas Woerner
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Get VARS from IPA
|
||||
ipasmartcard_server_get_vars:
|
||||
register: ipasmartcard_server_vars
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
NSS_OCSP_ENABLED:
|
||||
description:
|
||||
Empty string for newer systems using ssl.conf and not nss.conf for
|
||||
HTTP instance where OCSP_ENABLED and OCSP_DIRECTIVE are defined in
|
||||
ipaserver.install.httpinstance, else NSS_OCSP_ENABLED imported from
|
||||
ipaserver.install.httpinstance.
|
||||
returned: always
|
||||
type: str
|
||||
NSS_OCSP_DIRECTIVE:
|
||||
description:
|
||||
Empty string for newer systems using ssl.conf and not nss.conf for
|
||||
HTTP instance where OCSP_ENABLED and OCSP_DIRECTIVE are defined in
|
||||
ipaserver.install.httpinstance, else NSSOCSP.
|
||||
returned: always
|
||||
type: str
|
||||
NSS_NICKNAME_DIRECTIVE:
|
||||
description:
|
||||
Empty string for newer systems using ssl.conf and not nss.conf for
|
||||
HTTP instance where OCSP_ENABLED and OCSP_DIRECTIVE are defined in
|
||||
ipaserver.install.httpinstance, else NSSNickname
|
||||
returned: always
|
||||
type: str
|
||||
OCSP_ENABLED:
|
||||
description:
|
||||
OCSP_ENABLED imported from ipaserver.install.httpinstance, if import
|
||||
succeeds, else ""
|
||||
returned: always
|
||||
type: str
|
||||
OCSP_DIRECTIVE:
|
||||
description:
|
||||
OCSP_DIRECTIVE imported from ipaserver.install.httpinstance, if import
|
||||
succeeds, else ""
|
||||
returned: always
|
||||
type: str
|
||||
HTTPD_SSL_CONF:
|
||||
description: paths.HTTPD_SSL_CONF from ipaplatform
|
||||
returned: always
|
||||
type: str
|
||||
HTTPD_NSS_CONF:
|
||||
description: paths.HTTPD_NSS_CONF from ipaplatform
|
||||
returned: always
|
||||
type: str
|
||||
HTTPD_ALIAS_DIR:
|
||||
description: paths.HTTPD_ALIAS_DIR from ipaplatform
|
||||
returned: always
|
||||
type: str
|
||||
allow_httpd_ifp:
|
||||
description:
|
||||
True if sssd_enable_ifp can be imported from ipaclient.install.client,
|
||||
else false.
|
||||
returned: always
|
||||
type: bool
|
||||
NSS_DB_DIR:
|
||||
description: paths.NSS_DB_DIR from ipaplatform
|
||||
returned: always
|
||||
type: str
|
||||
USE_AUTHSELECT:
|
||||
description: True if "AUTHSELECT" is defined in paths
|
||||
returned: always
|
||||
type: bool
|
||||
python_interpreter:
|
||||
description: Python interpreter from sys.executable
|
||||
returned: always
|
||||
type: str
|
||||
'''
|
||||
|
||||
import sys
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ipaplatform.paths import paths
|
||||
try:
|
||||
from ipaserver.install.httpinstance import OCSP_ENABLED, OCSP_DIRECTIVE
|
||||
NSS_OCSP_ENABLED = ""
|
||||
NSS_OCSP_DIRECTIVE = ""
|
||||
NSS_NICKNAME_DIRECTIVE = ""
|
||||
except ImportError:
|
||||
from ipaserver.install.httpinstance import NSS_OCSP_ENABLED
|
||||
NSS_OCSP_DIRECTIVE = "NSSOCSP"
|
||||
NSS_NICKNAME_DIRECTIVE = "NSSNickname"
|
||||
OCSP_ENABLED = ""
|
||||
OCSP_DIRECTIVE = ""
|
||||
try:
|
||||
from ipaclient.install.client import sssd_enable_ifp
|
||||
except ImportError:
|
||||
sssd_enable_ifp = None
|
||||
|
||||
|
||||
def main():
|
||||
ansible_module = AnsibleModule(
|
||||
argument_spec={},
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
ansible_module.exit_json(changed=False,
|
||||
NSS_OCSP_ENABLED=NSS_OCSP_ENABLED,
|
||||
NSS_OCSP_DIRECTIVE=NSS_OCSP_DIRECTIVE,
|
||||
NSS_NICKNAME_DIRECTIVE=NSS_NICKNAME_DIRECTIVE,
|
||||
OCSP_ENABLED=OCSP_ENABLED,
|
||||
OCSP_DIRECTIVE=OCSP_DIRECTIVE,
|
||||
HTTPD_SSL_CONF=paths.HTTPD_SSL_CONF,
|
||||
HTTPD_NSS_CONF=paths.HTTPD_NSS_CONF,
|
||||
HTTPD_ALIAS_DIR=paths.HTTPD_ALIAS_DIR,
|
||||
allow_httpd_ifp=sssd_enable_ifp is not None,
|
||||
NSS_DB_DIR=paths.NSS_DB_DIR,
|
||||
USE_AUTHSELECT=hasattr(paths, "AUTHSELECT"),
|
||||
python_interpreter=sys.executable)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,110 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Based on ipa-replica-install code
|
||||
#
|
||||
# Copyright (C) 2022 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.0',
|
||||
'supported_by': 'community',
|
||||
'status': ['preview'],
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ipasmartcard_server_validate_ca_certs
|
||||
short description: Validate CA certs
|
||||
description: Validate CA certs
|
||||
options:
|
||||
ca_cert_files:
|
||||
description:
|
||||
List of files containing CA certificates for the service certificate
|
||||
files
|
||||
required: yes
|
||||
author:
|
||||
- Thomas Woerner
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
import os.path
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
try:
|
||||
from ipalib import x509
|
||||
except ImportError:
|
||||
x509 = None
|
||||
|
||||
|
||||
def main():
|
||||
ansible_module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
ca_cert_files=dict(required=False, type='list', default=[]),
|
||||
),
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
# get parameters #
|
||||
|
||||
ca_cert_files = ansible_module.params.get('ca_cert_files')
|
||||
|
||||
# import check #
|
||||
|
||||
if x509 is None:
|
||||
ansible_module.fail_json(msg="Failed to import x509 from ipalib")
|
||||
|
||||
# validate ca certs #
|
||||
|
||||
if ca_cert_files is not None:
|
||||
if not isinstance(ca_cert_files, list):
|
||||
ansible_module.fail_json(
|
||||
msg="Expected list, got %s" % repr(ca_cert_files))
|
||||
# remove duplicates
|
||||
ca_cert_files = list(dict.fromkeys(ca_cert_files))
|
||||
# validate
|
||||
for cert in ca_cert_files:
|
||||
if not os.path.exists(cert):
|
||||
ansible_module.fail_json(msg="'%s' does not exist" % cert)
|
||||
if not os.path.isfile(cert):
|
||||
ansible_module.fail_json(msg="'%s' is not a file" % cert)
|
||||
if not os.path.isabs(cert):
|
||||
ansible_module.fail_json(
|
||||
msg="'%s' is not an absolute file path" % cert)
|
||||
try:
|
||||
x509.load_certificate_from_file(cert)
|
||||
except Exception:
|
||||
ansible_module.fail_json(
|
||||
msg="'%s' is not a valid certificate file" % cert)
|
||||
|
||||
# exit #
|
||||
|
||||
ansible_module.exit_json(changed=False,
|
||||
ca_cert_files=ca_cert_files)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
22
roles/ipasmartcard_server/meta/main.yml
Normal file
22
roles/ipasmartcard_server/meta/main.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
dependencies: []
|
||||
|
||||
galaxy_info:
|
||||
author: Thomas Woerner
|
||||
description: A role to setup IPA server(s) for Smart Card authentication
|
||||
company: Red Hat, Inc
|
||||
license: GPLv3
|
||||
min_ansible_version: 2.8
|
||||
platforms:
|
||||
- name: Fedora
|
||||
versions:
|
||||
- all
|
||||
- name: EL
|
||||
versions:
|
||||
- 7
|
||||
- 8
|
||||
galaxy_tags:
|
||||
- identity
|
||||
- ipa
|
||||
- freeipa
|
||||
- smartcard
|
||||
247
roles/ipasmartcard_server/tasks/main.yml
Normal file
247
roles/ipasmartcard_server/tasks/main.yml
Normal file
@@ -0,0 +1,247 @@
|
||||
---
|
||||
# tasks file for ipasmartcard_server role
|
||||
|
||||
- name: Uninstall smartcard server
|
||||
ansible.builtin.fail: msg="Uninstalling smartcard for IPA is not supported"
|
||||
when: state|default('present') == 'absent'
|
||||
|
||||
- name: Import variables specific to distribution
|
||||
ansible.builtin.include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "vars/{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_version'] }}.yml"
|
||||
- "vars/{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_major_version'] }}.yml"
|
||||
- "vars/{{ ansible_facts['distribution'] }}.yml"
|
||||
# os_family is used as a fallback for distros which are not currently
|
||||
# supported, but are based on a supported distro family. For example,
|
||||
# Oracle, Rocky, Alma and Alibaba linux, which are all "RedHat" based.
|
||||
- "vars/{{ ansible_facts['os_family'] }}-{{ ansible_facts['distribution_version'] }}.yml"
|
||||
- "vars/{{ ansible_facts['os_family'] }}-{{ ansible_facts['distribution_major_version'] }}.yml"
|
||||
- "vars/{{ ansible_facts['os_family'] }}.yml"
|
||||
# If neither distro nor family is supported, try a default configuration.
|
||||
- "vars/default.yml"
|
||||
|
||||
- block:
|
||||
|
||||
# CA CERTS
|
||||
|
||||
# Fail on empty "ipasmartcard_server_ca_certs"
|
||||
- name: Fail on empty "ipasmartcard_server_ca_certs"
|
||||
ansible.builtin.fail: msg="No CA certs given in 'ipasmartcard_server_ca_certs'"
|
||||
when: ipasmartcard_server_ca_certs is not defined or
|
||||
ipasmartcard_server_ca_certs | length < 1
|
||||
|
||||
# Validate ipasmartcard_server_ca_certs
|
||||
|
||||
- name: Validate CA certs "{{ ipasmartcard_server_ca_certs }}"
|
||||
ipasmartcard_server_validate_ca_certs:
|
||||
ca_cert_files: "{{ ipasmartcard_server_ca_certs }}"
|
||||
register: result_validate_ca_certs
|
||||
|
||||
# INSTALL bind-utils
|
||||
|
||||
- name: Ensure {{ ipasmartcard_server_bindutils_packages }} are installed
|
||||
ansible.builtin.package:
|
||||
name: "{{ ipasmartcard_server_bindutils_packages }}"
|
||||
state: present
|
||||
when: ipaserver_install_packages | bool
|
||||
|
||||
# KINIT
|
||||
|
||||
- name: Set default principal if not given
|
||||
ansible.builtin.set_fact:
|
||||
ipaadmin_principal: admin
|
||||
when: ipaadmin_principal is undefined
|
||||
|
||||
- name: kinit using "{{ ipaadmin_principal }}" password
|
||||
ansible.builtin.command: kinit "{{ ipaadmin_principal }}"
|
||||
args:
|
||||
stdin: "{{ ipaadmin_password }}"
|
||||
when: ipaadmin_password is defined
|
||||
|
||||
- name: kinit using "{{ ipaadmin_principal }}" keytab
|
||||
ansible.builtin.command: kinit -kt "{{ ipaadmin_keytab }}" "{{ ipaadmin_principal }}"
|
||||
when: ipaadmin_keytab is defined
|
||||
|
||||
# IS MASTER
|
||||
|
||||
- name: Check that this is an IPA master
|
||||
ansible.builtin.command: ipa server-show --raw "{{ ipaserver_hostname | default(ansible_facts['fqdn']) }}"
|
||||
register: result_ipa_server_show
|
||||
|
||||
- name: Fail if not an IPA server
|
||||
ansible.builtin.fail: msg="Not an IPA server"
|
||||
when: result_ipa_server_show.failed
|
||||
|
||||
- name: Get Domain from server-find server name
|
||||
ansible.builtin.set_fact:
|
||||
ipaserver_domain: "{{ (result_ipa_server_show.stdout | regex_search('cn: (.+)', '\\1'))[0].split('.')[1:] | join ('.') }}"
|
||||
when: ipaserver_domain is not defined
|
||||
|
||||
- name: Get ipa-ca records
|
||||
ansible.builtin.command: "dig +short ipa-ca.{{ ipaserver_domain }}"
|
||||
register: result_get_ipaca_records
|
||||
|
||||
- name: Fail if ipa-ca records are not resolvable
|
||||
ansible.builtin.fail: msg="ipa-ca records are not resolvable"
|
||||
when: result_get_ipaca_records.failed or
|
||||
result_get_ipaca_records.stdout | length == 0
|
||||
|
||||
# GET VARS FROM IPA
|
||||
|
||||
- name: Get VARS from IPA
|
||||
ipasmartcard_server_get_vars:
|
||||
register: ipasmartcard_server_vars
|
||||
|
||||
# ENABLE NSS OCSP
|
||||
|
||||
- name: Enable the OCSP directive in nss.conf
|
||||
ansible.builtin.script: ipasmartcard_server_enable_ocsp_directive.sh
|
||||
"{{ ipasmartcard_server_vars.NSS_OCSP_DIRECTIVE }}"
|
||||
"{{ ipasmartcard_server_vars.HTTPD_NSS_CONF }}"
|
||||
when: ipasmartcard_server_vars.NSS_OCSP_ENABLED | length > 0
|
||||
|
||||
# MARK NSS HTTPD CERT AS TRUSTED
|
||||
|
||||
- name: Mark HTTPD CERT as trusted
|
||||
ansible.builtin.script: ipasmartcard_server_mark_httpd_cert_as_trusted.sh
|
||||
"{{ ipasmartcard_server_vars.NSS_OCSP_DIRECTIVE }}"
|
||||
"{{ ipasmartcard_server_vars.HTTPD_NSS_CONF }}"
|
||||
"{{ ipasmartcard_server_vars.NSS_NICKNAME_DIRECTIVE }}"
|
||||
"{{ ipasmartcard_server_vars.HTTPD_ALIAS_DIR }}"
|
||||
when: ipasmartcard_server_vars.NSS_OCSP_ENABLED | length > 0
|
||||
|
||||
# ENABLE SSL OCSP
|
||||
|
||||
- name: Enable the OCSP directive in ssl.conf
|
||||
ansible.builtin.script: ipasmartcard_server_enable_ocsp_directive.sh
|
||||
"{{ ipasmartcard_server_vars.OCSP_DIRECTIVE }}"
|
||||
"{{ ipasmartcard_server_vars.HTTPD_SSL_CONF }}"
|
||||
when: ipasmartcard_server_vars.OCSP_ENABLED | length > 0
|
||||
|
||||
# Restart apache
|
||||
|
||||
- name: Restart apache
|
||||
ansible.builtin.service:
|
||||
name: httpd
|
||||
state: restarted
|
||||
|
||||
# RECORD HTTPD OCSP STATUS
|
||||
|
||||
# Store the NSS OCSP upgrade state
|
||||
|
||||
- name: Store NSS OCSP upgrade state
|
||||
ansible.builtin.command: "{{ ipasmartcard_server_vars.python_interpreter }}"
|
||||
args:
|
||||
stdin: |
|
||||
from ipaserver.install import sysupgrade
|
||||
sysupgrade.set_upgrade_state("httpd", "{{ ipasmartcard_server_vars.NSS_OCSP_DIRECTIVE }}", True)
|
||||
when: ipasmartcard_server_vars.NSS_OCSP_ENABLED | length > 0
|
||||
|
||||
# Store the SSL OCSP upgrade state
|
||||
|
||||
- name: Store SSL OCSP upgrade state
|
||||
ansible.builtin.command: "{{ ipasmartcard_server_vars.python_interpreter }}"
|
||||
args:
|
||||
stdin: |
|
||||
from ipaserver.install import sysupgrade
|
||||
sysupgrade.set_upgrade_state("httpd", "{{ ipasmartcard_server_vars.OCSP_DIRECTIVE }}", True)
|
||||
when: ipasmartcard_server_vars.OCSP_ENABLED | length > 0
|
||||
|
||||
# check whether PKINIT is configured on the master
|
||||
|
||||
- name: Enable PKINIT
|
||||
ansible.builtin.command: ipa-pkinit-manage enable
|
||||
|
||||
# Enable OK-AS-DELEGATE flag on the HTTP principal
|
||||
# This enables smart card login to WebUI
|
||||
|
||||
- name: Enable OK-AS-DELEGATE flag on the HTTP principal
|
||||
ipaservice:
|
||||
name: "HTTP/{{ ipaserver_hostname | default(ansible_facts['fqdn']) }}"
|
||||
ok_to_auth_as_delegate: yes
|
||||
|
||||
# HTTPD IFP
|
||||
|
||||
- block:
|
||||
|
||||
# Allow Apache to access SSSD IFP
|
||||
|
||||
- name: Allow Apache to access SSSD IFP
|
||||
ansible.builtin.command: "{{ ipasmartcard_server_vars.python_interpreter }}"
|
||||
args:
|
||||
stdin: |
|
||||
import SSSDConfig
|
||||
from ipaclient.install.client import sssd_enable_ifp
|
||||
from ipaplatform.paths import paths
|
||||
c = SSSDConfig.SSSDConfig()
|
||||
c.import_config()
|
||||
sssd_enable_ifp(c, allow_httpd=True)
|
||||
c.write(paths.SSSD_CONF)
|
||||
when: ipasmartcard_server_vars.OCSP_ENABLED | length > 0
|
||||
|
||||
# Restart sssd
|
||||
|
||||
- name: Restart sssd
|
||||
ansible.builtin.service:
|
||||
name: sssd
|
||||
state: restarted
|
||||
|
||||
when: ipasmartcard_server_vars.allow_httpd_ifp
|
||||
|
||||
# Ensure /etc/sssd/pki exists
|
||||
|
||||
- block:
|
||||
- name: Ensure /etc/sssd/pki exists
|
||||
ansible.builtin.file:
|
||||
path: /etc/sssd/pki
|
||||
state: directory
|
||||
mode: 0711
|
||||
|
||||
- name: Ensure /etc/sssd/pki/sssd_auth_ca_db.pem is absent
|
||||
ansible.builtin.file:
|
||||
path: /etc/sssd/pki/sssd_auth_ca_db.pem
|
||||
state: absent
|
||||
|
||||
when: ipasmartcard_server_vars.USE_AUTHSELECT
|
||||
|
||||
# Upload smartcard CA certificates to systemwide db
|
||||
|
||||
- name: Upload smartcard CA certificates to systemwide db
|
||||
ansible.builtin.script: ipasmartcard_server_add_ca_to_systemwide_db.sh
|
||||
"{{ item }}"
|
||||
"{{ ipasmartcard_server_vars.NSS_DB_DIR }}"
|
||||
with_items: "{{ result_validate_ca_certs.ca_cert_files }}"
|
||||
|
||||
# Newer version of sssd use OpenSSL and read the CA certs
|
||||
# from /etc/sssd/pki/sssd_auth_ca_db.pem
|
||||
|
||||
- name: Add CA certs to /etc/sssd/pki/sssd_auth_ca_db.pem
|
||||
ansible.builtin.script: ipasmartcard_server_add_ca_to_sssd_auth_ca_db.sh
|
||||
"{{ item }}"
|
||||
/etc/sssd/pki/sssd_auth_ca_db.pem
|
||||
with_items: "{{ result_validate_ca_certs.ca_cert_files }}"
|
||||
when: ipasmartcard_server_vars.USE_AUTHSELECT
|
||||
|
||||
# Install smartcard signing CA certs
|
||||
|
||||
- name: Install smartcard signing CA certs
|
||||
ansible.builtin.command: ipa-cacert-manage install "{{ item }}" -t CT,C,C
|
||||
with_items: "{{ result_validate_ca_certs.ca_cert_files }}"
|
||||
|
||||
# Update ipa CA certificate store
|
||||
|
||||
- name: Update ipa CA certificate store
|
||||
ansible.builtin.command: ipa-certupdate
|
||||
|
||||
# Restart krb5kdc
|
||||
|
||||
- name: Restart krb5kdc
|
||||
ansible.builtin.service:
|
||||
name: krb5kdc
|
||||
state: restarted
|
||||
|
||||
### ALWAYS ###
|
||||
|
||||
always:
|
||||
- name: kdestroy
|
||||
ansible.builtin.command: kdestroy -A
|
||||
2
roles/ipasmartcard_server/vars/default.yml
Normal file
2
roles/ipasmartcard_server/vars/default.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
---
|
||||
ipasmartcard_server_bindutils_packages: [ "bind-utils" ]
|
||||
@@ -71,7 +71,8 @@ ignored-modules =
|
||||
ipaplatform, ipaplatform.paths, ipaplatform.tasks, ipapython.admintool,
|
||||
ipaserver.install.installutils, ipaserver.install.server.install,
|
||||
ipaserver.install,
|
||||
ipaclient.install.ipachangeconf, ipaclient.install.client
|
||||
ipaclient.install.ipachangeconf, ipaclient.install.client,
|
||||
ipaserver.dcerpc
|
||||
|
||||
[pylint.REFACTORING]
|
||||
max-nested-blocks = 9
|
||||
|
||||
@@ -63,6 +63,24 @@ IPA_SERVER_HOST=<ipaserver_host_or_ip> pytest -rs
|
||||
|
||||
For a complete list of options check `pytest --help`.
|
||||
|
||||
### Disabling and enabling playbook tests
|
||||
|
||||
Sometimes it is useful to enable or disable specific playbook tests. To only run a subset of modules or tests, use the variables IPA_ENABLED_MODULES and IPA ENABLED_TESTS, to define a comma-separated list of modules or tests to be enabled. Any test or module not in the list will not be executed. For example, to run only `sudorule` and `sudocmd` tests:
|
||||
|
||||
```
|
||||
IPA_ENABLE_MODULES="sudorule,sudocmd" IPA_SERVER_HOST=<ipaserver_host_or_ip> pytest
|
||||
```
|
||||
|
||||
If all but a few selected tests are to be executed, use the IPA_DISABLED_MODULES or IPA_DISABLED_TESTS. For example, to run all, but "test_service_certificate" test:
|
||||
|
||||
```
|
||||
IPA_DISABLED_TESTS=test_service_certificate IPA_SERVER_HOST=<ipaserver_host_or_ip> pytest
|
||||
```
|
||||
|
||||
If none of this variables are defined, all tests will be executed.
|
||||
|
||||
To configure the tests that will run for your pull request, add a TEMP commit, with the configuration defined in the file `tests/azure/templates/variables.yml`. Set the variables `ipa_enable_modules`, `ipa_enable_tests`, `ipa_disable_modules`, and `ipa_disable_tests`, in the same way as the equivalent environment variables.
|
||||
|
||||
### Types of tests
|
||||
|
||||
#### Playbook tests
|
||||
@@ -92,19 +110,21 @@ pip install molecule[docker]>=3
|
||||
|
||||
Now you can start a test container using the following command:
|
||||
```
|
||||
molecule create -s centos-8
|
||||
molecule create -s c8s
|
||||
```
|
||||
|
||||
Note: Currently the containers available for running the tests are:
|
||||
* fedora-latest
|
||||
* centos-7
|
||||
* centos-8
|
||||
* c8s
|
||||
* c9s
|
||||
|
||||
### Running the tests inside the container
|
||||
|
||||
To run the tests you will use pytest (works the same as for VMs).
|
||||
|
||||
```
|
||||
RUN_TESTS_IN_DOCKER=1 IPA_SERVER_HOST=centos-8 pytest
|
||||
RUN_TESTS_IN_DOCKER=1 IPA_SERVER_HOST=c8s pytest
|
||||
```
|
||||
|
||||
### Cleaning up after tests
|
||||
@@ -112,11 +132,12 @@ RUN_TESTS_IN_DOCKER=1 IPA_SERVER_HOST=centos-8 pytest
|
||||
After running the tests you should probably destroy the test container using:
|
||||
|
||||
```
|
||||
molecule destroy -s centos-8
|
||||
molecule destroy -s c8s
|
||||
```
|
||||
|
||||
See [Running the tests](#running-the-tests) section for more information on available options.
|
||||
|
||||
|
||||
## Upcoming/desired improvements:
|
||||
|
||||
* A script to pre-config the complete test environment using virsh.
|
||||
|
||||
@@ -9,55 +9,55 @@ stages:
|
||||
|
||||
# Fedora
|
||||
|
||||
- stage: FedoraLatest_Ansible_2_9
|
||||
- stage: Fedora_Latest
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/group_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: fedora-latest
|
||||
ansible_version: ">=2.9,<2.10"
|
||||
ansible_version: "-core >=2.12,<2.13"
|
||||
|
||||
# Galaxy on Fedora
|
||||
|
||||
- stage: Galaxy_Fedora_Latest
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/galaxy_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: fedora-latest
|
||||
ansible_version: "-core >=2.12,<2.13"
|
||||
|
||||
# CentOS 9 Stream
|
||||
|
||||
- stage: c9s_Ansible_2_9
|
||||
- stage: CentOS_9_Stream
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/group_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: c9s
|
||||
ansible_version: ">=2.9,<2.10"
|
||||
ansible_version: "-core >=2.12,<2.13"
|
||||
|
||||
# CentOS 8 Stream
|
||||
|
||||
- stage: c8s_Ansible_2_9
|
||||
- stage: CentOS_8_Stream
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/group_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: c8s
|
||||
ansible_version: ">=2.9,<2.10"
|
||||
|
||||
# # CentOS 8
|
||||
#
|
||||
# - stage: CentOS8_Ansible_2_9
|
||||
# dependsOn: []
|
||||
# jobs:
|
||||
# - template: templates/group_tests.yml
|
||||
# parameters:
|
||||
# build_number: $(Build.BuildNumber)
|
||||
# scenario: centos-8
|
||||
# ansible_version: ">=2.9,<2.10"
|
||||
ansible_version: "-core >=2.12,<2.13"
|
||||
|
||||
# CentOS 7
|
||||
|
||||
- stage: CentOS7_Ansible_2_9
|
||||
- stage: CentOS_7
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/group_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: centos-7
|
||||
ansible_version: ">=2.9,<2.10"
|
||||
ansible_version: "-core >=2.12,<2.13"
|
||||
|
||||
@@ -21,12 +21,6 @@ jobs:
|
||||
container_name: centos-7
|
||||
build_scenario_name: centos-7-build
|
||||
|
||||
# - template: templates/build_container.yml
|
||||
# parameters:
|
||||
# job_name_suffix: Centos8
|
||||
# container_name: centos-8
|
||||
# build_scenario_name: centos-8-build
|
||||
|
||||
- template: templates/build_container.yml
|
||||
parameters:
|
||||
job_name_suffix: C8S
|
||||
@@ -44,3 +38,9 @@ jobs:
|
||||
job_name_suffix: FedoraLatest
|
||||
container_name: fedora-latest
|
||||
build_scenario_name: fedora-latest-build
|
||||
|
||||
- template: templates/build_container.yml
|
||||
parameters:
|
||||
job_name_suffix: FedoraRawhide
|
||||
container_name: fedora-rawhide
|
||||
build_scenario_name: fedora-rawhide-build
|
||||
|
||||
@@ -16,15 +16,6 @@ stages:
|
||||
|
||||
# Fedora
|
||||
|
||||
- stage: FedoraLatest_Ansible_2_9
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/group_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: fedora-latest
|
||||
ansible_version: ">=2.9,<2.10"
|
||||
|
||||
- stage: FedoraLatest_Ansible_Core_2_11
|
||||
dependsOn: []
|
||||
jobs:
|
||||
@@ -52,16 +43,92 @@ stages:
|
||||
scenario: fedora-latest
|
||||
ansible_version: ""
|
||||
|
||||
# CentoOS 9 Stream
|
||||
|
||||
- stage: c9s_Ansible_2_9
|
||||
- stage: FedoraLatest_Ansible_Core_latest
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/group_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: c9s
|
||||
ansible_version: ">=2.9,<2.10"
|
||||
scenario: fedora-latest
|
||||
ansible_version: "-core"
|
||||
|
||||
# Galaxy on Fedora
|
||||
|
||||
- stage: Galaxy_FedoraLatest_Ansible_Core_2_11
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/galaxy_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: fedora-latest
|
||||
ansible_version: "-core >=2.11,<2.12"
|
||||
|
||||
- stage: Galaxy_FedoraLatest_Ansible_Core_2_12
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/galaxy_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: fedora-latest
|
||||
ansible_version: "-core >=2.12,<2.13"
|
||||
|
||||
- stage: Galaxy_FedoraLatest_Ansible_latest
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/galaxy_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: fedora-latest
|
||||
ansible_version: ""
|
||||
|
||||
- stage: Galaxy_FedoraLatest_Ansible_Core_latest
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/galaxy_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: fedora-latest
|
||||
ansible_version: "-core"
|
||||
|
||||
# Fedora Rawhide
|
||||
|
||||
- stage: FedoraRawhide_Ansible_Core_2_11
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/group_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: fedora-rawhide
|
||||
ansible_version: "-core >=2.11,<2.12"
|
||||
|
||||
- stage: FedoraRawhide_Ansible_Core_2_12
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/group_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: fedora-rawhide
|
||||
ansible_version: "-core >=2.12,<2.13"
|
||||
|
||||
- stage: FedoraRawhide_Ansible_latest
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/group_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: fedora-rawhide
|
||||
ansible_version: ""
|
||||
|
||||
- stage: FedoraRawhide_Ansible_Core_latest
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/group_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: fedora-rawhide
|
||||
ansible_version: "-core"
|
||||
|
||||
# CentoOS 9 Stream
|
||||
|
||||
- stage: c9s_Ansible_Core_2_11
|
||||
dependsOn: []
|
||||
@@ -90,16 +157,16 @@ stages:
|
||||
scenario: c9s
|
||||
ansible_version: ""
|
||||
|
||||
# CentOS 8 Stream
|
||||
|
||||
- stage: c8s_Ansible_2_9
|
||||
- stage: c9s_Ansible_Core_latest
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/group_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: c8s
|
||||
ansible_version: ">=2.9,<2.10"
|
||||
scenario: c9s
|
||||
ansible_version: "-core"
|
||||
|
||||
# CentOS 8 Stream
|
||||
|
||||
- stage: c8s_Ansible_Core_2_11
|
||||
dependsOn: []
|
||||
@@ -128,54 +195,16 @@ stages:
|
||||
scenario: c8s
|
||||
ansible_version: ""
|
||||
|
||||
# # CentOS 8
|
||||
#
|
||||
# - stage: CentOS8_Ansible_2_9
|
||||
# dependsOn: []
|
||||
# jobs:
|
||||
# - template: templates/group_tests.yml
|
||||
# parameters:
|
||||
# build_number: $(Build.BuildNumber)
|
||||
# scenario: centos-8
|
||||
# ansible_version: ">=2.9,<2.10"
|
||||
#
|
||||
# - stage: CentOS8_Ansible_Core_2_11
|
||||
# dependsOn: []
|
||||
# jobs:
|
||||
# - template: templates/group_tests.yml
|
||||
# parameters:
|
||||
# build_number: $(Build.BuildNumber)
|
||||
# scenario: centos-8
|
||||
# ansible_version: "-core >=2.11,<2.12"
|
||||
#
|
||||
# - stage: CentOS8_Ansible_Core_2_12
|
||||
# dependsOn: []
|
||||
# jobs:
|
||||
# - template: templates/group_tests.yml
|
||||
# parameters:
|
||||
# build_number: $(Build.BuildNumber)
|
||||
# scenario: centos-8
|
||||
# ansible_version: "-core >=2.12,<2.13"
|
||||
#
|
||||
# - stage: CentOS8_Ansible_latest
|
||||
# dependsOn: []
|
||||
# jobs:
|
||||
# - template: templates/group_tests.yml
|
||||
# parameters:
|
||||
# build_number: $(Build.BuildNumber)
|
||||
# scenario: centos-8
|
||||
# ansible_version: ""
|
||||
|
||||
# CentOS 7
|
||||
|
||||
- stage: CentOS7_Ansible_2_9
|
||||
- stage: c8s_Ansible_Core_latest
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/group_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: centos-7
|
||||
ansible_version: ">=2.9,<2.10"
|
||||
scenario: c8s
|
||||
ansible_version: "-core"
|
||||
|
||||
# CentOS 7
|
||||
|
||||
- stage: CentOS7_Ansible_Core_2_11
|
||||
dependsOn: []
|
||||
@@ -203,3 +232,12 @@ stages:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: centos-7
|
||||
ansible_version: ""
|
||||
|
||||
- stage: CentOS7_Ansible_Core_latest
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/group_tests.yml
|
||||
parameters:
|
||||
build_number: $(Build.BuildNumber)
|
||||
scenario: centos-7
|
||||
ansible_version: "-core"
|
||||
|
||||
58
tests/azure/templates/galaxy_pytest_script.yml
Normal file
58
tests/azure/templates/galaxy_pytest_script.yml
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
parameters:
|
||||
- name: build_number
|
||||
type: string
|
||||
- name: scenario
|
||||
type: string
|
||||
default: fedora-latest
|
||||
- name: ansible_version
|
||||
type: string
|
||||
default: ""
|
||||
- name: python_version
|
||||
type: string
|
||||
default: 3.x
|
||||
|
||||
jobs:
|
||||
- job: Test_PyTests
|
||||
displayName: Run pytests on ${{ parameters.scenario }}
|
||||
timeoutInMinutes: 120
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: '${{ parameters.python_version }}'
|
||||
|
||||
- script: |
|
||||
pip install \
|
||||
"molecule[docker]>=3" \
|
||||
"ansible${{ parameters.ansible_version }}"
|
||||
displayName: Install molecule and Ansible
|
||||
|
||||
- script: ansible-galaxy collection install community.docker ansible.posix
|
||||
displayName: Install Ansible collections
|
||||
|
||||
- script: pip install -r requirements-tests.txt
|
||||
displayName: Install dependencies
|
||||
- script: |
|
||||
utils/build-galaxy-release.sh -i
|
||||
molecule create -s ${{ parameters.scenario }}
|
||||
displayName: Setup test container
|
||||
env:
|
||||
ANSIBLE_LIBRARY: ./molecule
|
||||
|
||||
- script: |
|
||||
cd ~/.ansible/collections/ansible_collections/freeipa/ansible_freeipa
|
||||
pytest \
|
||||
-m "not playbook" \
|
||||
--verbose \
|
||||
--color=yes \
|
||||
--junit-xml=TEST-results-pytests.xml
|
||||
displayName: Run tests
|
||||
env:
|
||||
IPA_SERVER_HOST: ${{ parameters.scenario }}
|
||||
RUN_TESTS_IN_DOCKER: true
|
||||
|
||||
- task: PublishTestResults@2
|
||||
inputs:
|
||||
mergeTestResults: true
|
||||
testRunTitle: PlaybookTests-Build${{ parameters.build_number }}
|
||||
condition: succeededOrFailed()
|
||||
75
tests/azure/templates/galaxy_script.yml
Normal file
75
tests/azure/templates/galaxy_script.yml
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
parameters:
|
||||
- name: group_number
|
||||
type: number
|
||||
default: 1
|
||||
- name: number_of_groups
|
||||
type: number
|
||||
default: 1
|
||||
- name: scenario
|
||||
type: string
|
||||
default: fedora-latest
|
||||
- name: ansible_version
|
||||
type: string
|
||||
default: ""
|
||||
- name: python_version
|
||||
type: string
|
||||
default: 3.x
|
||||
- name: build_number
|
||||
type: string
|
||||
|
||||
|
||||
jobs:
|
||||
- job: Test_Group${{ parameters.group_number }}
|
||||
displayName: Run playbook tests ${{ parameters.scenario }} (${{ parameters.group_number }}/${{ parameters.number_of_groups }})
|
||||
timeoutInMinutes: 120
|
||||
variables:
|
||||
- template: variables.yaml
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: '${{ parameters.python_version }}'
|
||||
|
||||
- script: |
|
||||
pip install \
|
||||
"molecule[docker]>=3" \
|
||||
"ansible${{ parameters.ansible_version }}"
|
||||
displayName: Install molecule and Ansible
|
||||
|
||||
- script: ansible-galaxy collection install community.docker ansible.posix
|
||||
displayName: Install Ansible collections
|
||||
|
||||
- script: pip install -r requirements-tests.txt
|
||||
displayName: Install dependencies
|
||||
|
||||
- script: |
|
||||
utils/build-galaxy-release.sh -i
|
||||
molecule create -s ${{ parameters.scenario }}
|
||||
displayName: Setup test container
|
||||
env:
|
||||
ANSIBLE_LIBRARY: ./molecule
|
||||
|
||||
- script: |
|
||||
cd ~/.ansible/collections/ansible_collections/freeipa/ansible_freeipa
|
||||
pytest \
|
||||
-m "playbook" \
|
||||
--verbose \
|
||||
--color=yes \
|
||||
--test-group-count=${{ parameters.number_of_groups }} \
|
||||
--test-group=${{ parameters.group_number }} \
|
||||
--test-group-random-seed=97943259814 \
|
||||
--junit-xml=TEST-results-group-${{ parameters.group_number }}.xml
|
||||
displayName: Run playbook tests
|
||||
env:
|
||||
IPA_SERVER_HOST: ${{ parameters.scenario }}
|
||||
RUN_TESTS_IN_DOCKER: true
|
||||
IPA_DISABLED_MODULES: ${{ variables.ipa_disabled_modules }}
|
||||
IPA_DISABLED_TESTS: ${{ variables.ipa_disabled_tests }}
|
||||
IPA_ENABLED_MODULES: ${{ variables.ipa_enabled_modules }}
|
||||
IPA_ENABLED_TESTS: ${{ variables.ipa_enabled_tests }}
|
||||
|
||||
- task: PublishTestResults@2
|
||||
inputs:
|
||||
mergeTestResults: true
|
||||
testRunTitle: PlaybookTests-Build${{ parameters.build_number }}
|
||||
condition: succeededOrFailed()
|
||||
41
tests/azure/templates/galaxy_tests.yml
Normal file
41
tests/azure/templates/galaxy_tests.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
parameters:
|
||||
- name: scenario
|
||||
type: string
|
||||
default: fedora-latest
|
||||
- name: build_number
|
||||
type: string
|
||||
- name: ansible_version
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
jobs:
|
||||
- template: galaxy_script.yml
|
||||
parameters:
|
||||
group_number: 1
|
||||
number_of_groups: 3
|
||||
build_number: ${{ parameters.build_number }}
|
||||
scenario: ${{ parameters.scenario }}
|
||||
ansible_version: ${{ parameters.ansible_version }}
|
||||
|
||||
- template: galaxy_script.yml
|
||||
parameters:
|
||||
group_number: 2
|
||||
number_of_groups: 3
|
||||
build_number: ${{ parameters.build_number }}
|
||||
scenario: ${{ parameters.scenario }}
|
||||
ansible_version: ${{ parameters.ansible_version }}
|
||||
|
||||
- template: galaxy_script.yml
|
||||
parameters:
|
||||
group_number: 3
|
||||
number_of_groups: 3
|
||||
build_number: ${{ parameters.build_number }}
|
||||
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 }}
|
||||
@@ -18,11 +18,12 @@ parameters:
|
||||
- name: build_number
|
||||
type: string
|
||||
|
||||
|
||||
jobs:
|
||||
- job: Test_Group${{ parameters.group_number }}
|
||||
displayName: Run playbook tests ${{ parameters.scenario }} (${{ parameters.group_number }}/${{ parameters.number_of_groups }})
|
||||
timeoutInMinutes: 120
|
||||
variables:
|
||||
- template: variables.yaml
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
@@ -63,6 +64,10 @@ jobs:
|
||||
env:
|
||||
IPA_SERVER_HOST: ${{ parameters.scenario }}
|
||||
RUN_TESTS_IN_DOCKER: true
|
||||
IPA_DISABLED_MODULES: ${{ variables.ipa_disabled_modules }}
|
||||
IPA_DISABLED_TESTS: ${{ variables.ipa_disabled_tests }}
|
||||
IPA_ENABLED_MODULES: ${{ variables.ipa_enabled_modules }}
|
||||
IPA_ENABLED_TESTS: ${{ variables.ipa_enabled_tests }}
|
||||
|
||||
- task: PublishTestResults@2
|
||||
inputs:
|
||||
|
||||
@@ -16,6 +16,8 @@ jobs:
|
||||
- job: Test_PyTests
|
||||
displayName: Run pytests on ${{ parameters.scenario }}
|
||||
timeoutInMinutes: 120
|
||||
variables:
|
||||
- template: variables.yaml
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
@@ -53,6 +55,10 @@ jobs:
|
||||
env:
|
||||
IPA_SERVER_HOST: ${{ parameters.scenario }}
|
||||
RUN_TESTS_IN_DOCKER: true
|
||||
IPA_DISABLED_MODULES: ${{ variables.ipa_disabled_modules }}
|
||||
IPA_DISABLED_TESTS: ${{ variables.ipa_disabled_tests }}
|
||||
IPA_ENABLED_MODULES: ${{ variables.ipa_enabled_modules }}
|
||||
IPA_ENABLED_TESTS: ${{ variables.ipa_enabled_tests }}
|
||||
|
||||
- task: PublishTestResults@2
|
||||
inputs:
|
||||
|
||||
20
tests/azure/templates/variables.yaml
Normal file
20
tests/azure/templates/variables.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
#
|
||||
# Variables must be defined as comma separated lists.
|
||||
# For easier management of items to enable/disable,
|
||||
# use one test/module on each line, followed by a comma.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# disabled_modules: >-
|
||||
# dnsconfig,
|
||||
# group,
|
||||
# hostgroup
|
||||
#
|
||||
---
|
||||
variables:
|
||||
# ipa_enabled_modules: >-
|
||||
# ipa_enabled_tests: >-
|
||||
ipa_disabled_modules: >-
|
||||
dnsconfig,
|
||||
ipa_disabled_tests: >-
|
||||
test_dnsconfig_forwarders_ports
|
||||
@@ -13,14 +13,11 @@
|
||||
forwarders:
|
||||
- ip_address: 8.8.8.8
|
||||
- ip_address: 8.8.4.4
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
port: 53
|
||||
state: absent
|
||||
action: member
|
||||
|
||||
# Tests.
|
||||
- name: Set config to invalid IPv4.
|
||||
- name: Set forward with invalid IPv4.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
@@ -54,8 +51,6 @@
|
||||
forwarders:
|
||||
- ip_address: 8.8.8.8
|
||||
- ip_address: 8.8.4.4
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
port: 53
|
||||
forward_policy: only
|
||||
allow_sync_ptr: yes
|
||||
register: result
|
||||
@@ -68,8 +63,6 @@
|
||||
forwarders:
|
||||
- ip_address: 8.8.8.8
|
||||
- ip_address: 8.8.4.4
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
port: 53
|
||||
forward_policy: only
|
||||
allow_sync_ptr: yes
|
||||
register: result
|
||||
@@ -97,14 +90,15 @@
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Ensure forwarder 8.8.4.4 is present.
|
||||
- name: Check if forwarder 8.8.4.4 is present.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 8.8.4.4
|
||||
check_mode: yes
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Ensure forwarder 8.8.8.8 is present.
|
||||
ipadnsconfig:
|
||||
@@ -115,35 +109,16 @@
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Ensure forwarder 8.8.4.4 is present.
|
||||
- name: Check forwarder 8.8.4.4 is still present.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 8.8.4.4
|
||||
check_mode: yes
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Ensure forwarders are absent.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 8.8.4.4
|
||||
- ip_address: 8.8.8.8
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Ensure forwarders are absent, again.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 8.8.4.4
|
||||
- ip_address: 8.8.8.8
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Disable global forwarders.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
@@ -208,97 +183,6 @@
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Ensure all forwarders are absent.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 8.8.8.8
|
||||
- ip_address: 8.8.4.4
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
port: 53
|
||||
state: absent
|
||||
action: member
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Ensure all forwarders are absent, again.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 8.8.8.8
|
||||
- ip_address: 8.8.4.4
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
port: 53
|
||||
state: absent
|
||||
action: member
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Ensure forwarder is present.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 8.8.8.8
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Ensure forwarders is not present.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 8.8.4.4
|
||||
check_mode: yes
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Ensure forwarders are present.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 8.8.4.4
|
||||
- ip_address: 8.8.8.8
|
||||
action: member
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Ensure forwarders are present, again.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 8.8.4.4
|
||||
- ip_address: 8.8.8.8
|
||||
action: member
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Ensure another forwarder is present.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 8.8.4.4
|
||||
action: member
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Ensure forwarders are present.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 8.8.4.4
|
||||
- ip_address: 8.8.8.8
|
||||
action: member
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
# Cleanup.
|
||||
- name: Ensure forwarders are absent.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
@@ -306,8 +190,5 @@
|
||||
forwarders:
|
||||
- ip_address: 8.8.8.8
|
||||
- ip_address: 8.8.4.4
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
port: 53
|
||||
state: absent
|
||||
action: member
|
||||
|
||||
81
tests/dnsconfig/test_dnsconfig_forwarders_ports.yml
Normal file
81
tests/dnsconfig/test_dnsconfig_forwarders_ports.yml
Normal file
@@ -0,0 +1,81 @@
|
||||
---
|
||||
- name: Test dnsconfig forwarders with custom ports
|
||||
hosts: "{{ ipa_test_host | default('ipaserver') }}"
|
||||
become: true
|
||||
gather_facts: no
|
||||
|
||||
tasks:
|
||||
- block:
|
||||
# Setup.
|
||||
- name: Ensure forwarder with custom port is absent.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
port: 53
|
||||
state: absent
|
||||
action: member
|
||||
|
||||
# Tests.
|
||||
|
||||
- name: Ensure forwarder with custom port is present.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
port: 53
|
||||
state: present
|
||||
action: member
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Ensure forwarder with custom port is present, again.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
port: 53
|
||||
state: present
|
||||
action: member
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Ensure forwarder with custom port is absent.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
port: 53
|
||||
state: absent
|
||||
action: member
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
- name: Ensure forwarder with custom port is absent, again.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
port: 53
|
||||
state: absent
|
||||
action: member
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
always:
|
||||
|
||||
# Cleanup.
|
||||
- name: Ensure forwarder with custom port is absent.
|
||||
ipadnsconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
forwarders:
|
||||
- ip_address: 2001:4860:4860::8888
|
||||
port: 53
|
||||
state: absent
|
||||
action: member
|
||||
@@ -119,7 +119,7 @@
|
||||
name: local_id_range
|
||||
|
||||
- block:
|
||||
# Create trust with range_type: ipa-ad-trust-posix
|
||||
# Create trust with range_type: ipa-ad-trust
|
||||
- name: Create trust with range_type 'ipa-ad-trust'
|
||||
include_tasks: tasks_set_trust.yml
|
||||
vars:
|
||||
@@ -127,7 +127,7 @@
|
||||
trust_range_size: 200000
|
||||
trust_range_type: ipa-ad-trust
|
||||
|
||||
# Can't user secondary_rid_base with dom_sid/dom_name
|
||||
# Can't use secondary_rid_base with dom_sid/dom_name
|
||||
- name: Ensure AD-trust idrange is present
|
||||
ipaidrange:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
@@ -227,6 +227,50 @@
|
||||
name: ad_id_range
|
||||
state: absent
|
||||
|
||||
# Create trust with range_type: ipa-ad-trust-posix
|
||||
- name: Create trust with range_type 'ipa-ad-trust'
|
||||
include_tasks: tasks_set_trust.yml
|
||||
vars:
|
||||
trust_base_id: 10000000
|
||||
trust_range_size: 200000
|
||||
trust_range_type: ipa-ad-trust
|
||||
|
||||
- name: Ensure AD-trust idrange is present, with dom_name
|
||||
ipaidrange:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: ad_id_range
|
||||
base_id: 150000000
|
||||
range_size: 200000
|
||||
rid_base: 1000000
|
||||
idrange_type: ipa-ad-trust
|
||||
dom_name: "{{ adserver.domain }}"
|
||||
auto_private_groups: "false"
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
# Remove trust and idrange
|
||||
- name: Remove test trust.
|
||||
include_tasks: tasks_remove_trust.yml
|
||||
|
||||
- name: Ensure AD-trust idrange is absent
|
||||
ipaidrange:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: ad_id_range
|
||||
state: absent
|
||||
|
||||
# Remove trust and idrange
|
||||
- name: Remove test trust.
|
||||
include_tasks: tasks_remove_trust.yml
|
||||
|
||||
- name: Ensure AD-trust idrange is absent
|
||||
ipaidrange:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: ad_id_range
|
||||
state: absent
|
||||
|
||||
# Create trust with range_type: ipa-ad-trust-posix
|
||||
- name: Create trust with range_type 'ipa-ad-trust-posix'
|
||||
include_tasks: tasks_set_trust.yml
|
||||
@@ -235,7 +279,7 @@
|
||||
trust_range_size: 2000000
|
||||
trust_range_type: ipa-ad-trust-posix
|
||||
|
||||
# Can't user secondary_rid_base or rid_base with "ad-trust-posix"
|
||||
# Can't use secondary_rid_base or rid_base with "ad-trust-posix"
|
||||
- name: Ensure AD-trust-posix idrange is present
|
||||
ipaidrange:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
@@ -260,6 +304,51 @@
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
- name: Check if AD-trust-posix idrange is present, using dom_name
|
||||
ipaidrange:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: ad_posix_id_range
|
||||
base_id: 150000000
|
||||
range_size: 200000
|
||||
idrange_type: ipa-ad-trust-posix
|
||||
dom_name: "{{ adserver.domain }}"
|
||||
check_mode: yes
|
||||
register: result
|
||||
failed_when: result.changed or result.failed
|
||||
|
||||
# Remove trust and idrange
|
||||
- name: Remove test trust.
|
||||
include_tasks: tasks_remove_trust.yml
|
||||
|
||||
- name: Ensure AD-trust idrange is absent
|
||||
ipaidrange:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: ad_posix_id_range
|
||||
state: absent
|
||||
|
||||
# Create trust with range_type: ipa-ad-trust-posix
|
||||
- name: Create trust with range_type 'ipa-ad-trust-posix'
|
||||
include_tasks: tasks_set_trust.yml
|
||||
vars:
|
||||
trust_base_id: 10000000
|
||||
trust_range_size: 2000000
|
||||
trust_range_type: ipa-ad-trust-posix
|
||||
|
||||
# Can't use secondary_rid_base or rid_base with "ad-trust-posix"
|
||||
- name: Ensure AD-trust-posix idrange is present, with dom_name
|
||||
ipaidrange:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||
name: ad_posix_id_range
|
||||
base_id: 150000000
|
||||
range_size: 200000
|
||||
idrange_type: ipa-ad-trust-posix
|
||||
dom_name: "{{ adserver.domain }}"
|
||||
register: result
|
||||
failed_when: not result.changed or result.failed
|
||||
|
||||
always:
|
||||
# CLEANUP TEST ITEMS
|
||||
- name: Remove test trust.
|
||||
|
||||
@@ -64,18 +64,26 @@ class TestDNSZone(AnsibleFreeIPATestCase):
|
||||
def test_dnszone_disable(self):
|
||||
"""TC-30: Disable DNS Zone."""
|
||||
zone26 = "26testzone.test"
|
||||
self.check_details(["Active zone: TRUE"], "dnszone-find", [zone26])
|
||||
self.check_details(
|
||||
["Active zone: (TRUE|True)"], "dnszone-find", [zone26]
|
||||
)
|
||||
# Disable dns zone
|
||||
self.run_playbook(BASE_PATH + "dnszone_disable.yaml")
|
||||
self.check_details(["Active zone: FALSE"], "dnszone-find", [zone26])
|
||||
self.check_details(
|
||||
["Active zone: (FALSE|False)"], "dnszone-find", [zone26]
|
||||
)
|
||||
|
||||
def test_dnszone_enable(self):
|
||||
"""TC-31: Enable DNS Zone."""
|
||||
zone26 = "26testzone.test"
|
||||
self.check_details(["Active zone: FALSE"], "dnszone-find", [zone26])
|
||||
self.check_details(
|
||||
["Active zone: (FALSE|False)"], "dnszone-find", [zone26]
|
||||
)
|
||||
# Enable dns zone
|
||||
self.run_playbook(BASE_PATH + "dnszone_enable.yaml")
|
||||
self.check_details(["Active zone: TRUE"], "dnszone-find", [zone26])
|
||||
self.check_details(
|
||||
["Active zone: (TRUE|True)"], "dnszone-find", [zone26]
|
||||
)
|
||||
|
||||
def test_dnszone_name_from_ip(self):
|
||||
"""TC-35: Add dns zone with reverse zone IP. Bug#1845056."""
|
||||
|
||||
@@ -43,7 +43,6 @@ tests/sanity/sanity.sh shebang!skip
|
||||
tests/user/users.sh shebang!skip
|
||||
tests/user/users_absent.sh shebang!skip
|
||||
tests/utils.py pylint:ansible-format-automatic-specification
|
||||
tests/utils.py pylint:subprocess-run-check
|
||||
utils/ansible-doc-test shebang!skip
|
||||
utils/ansible-ipa-client-install shebang!skip
|
||||
utils/ansible-ipa-replica-install shebang!skip
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
TOPDIR=$(readlink -f "$(dirname "$0")/../..")
|
||||
pushd "${TOPDIR}" >/dev/null || exit 1
|
||||
|
||||
VENV=/tmp/ansible-test-venv
|
||||
ANSIBLE_COLLECTION=freeipa-ansible_freeipa
|
||||
|
||||
use_docker=$(which docker >/dev/null 2>&1 && echo "True" || echo "False")
|
||||
|
||||
virtualenv "$VENV"
|
||||
# shellcheck disable=SC1091
|
||||
source "$VENV"/bin/activate
|
||||
@@ -15,7 +20,8 @@ rm -f importer_result.json
|
||||
|
||||
utils/build-galaxy-release.sh
|
||||
|
||||
export GALAXY_IMPORTER_CONFIG=tests/sanity/galaxy-importer.cfg
|
||||
sed "s/LOCAL_IMAGE_DOCKER = True/LOCAL_IMAGE_DOCKER = ${use_docker}/" < tests/sanity/galaxy-importer.cfg > ${VENV}/galaxy-importer.cfg
|
||||
export GALAXY_IMPORTER_CONFIG=${VENV}/galaxy-importer.cfg
|
||||
|
||||
collection=$(ls -1 "$ANSIBLE_COLLECTION"-*.tar.gz)
|
||||
echo "Running: python -m galaxy_importer.main $collection"
|
||||
@@ -33,4 +39,6 @@ done < <(python -m galaxy_importer.main "$collection")
|
||||
|
||||
rm -rf "$VENV"
|
||||
|
||||
popd >/dev/null || exit 1
|
||||
|
||||
exit "$error"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
- block:
|
||||
- name: Get server name from hostname
|
||||
set_fact:
|
||||
ipa_server_name: "{{ ansible_facts['hostname'].split('.')[0] }}"
|
||||
ipa_server_name: "{{ ansible_facts['fqdn'].split('.')[0] }}"
|
||||
rescue:
|
||||
- name: Fallback to 'ipaserver'
|
||||
set_fact:
|
||||
@@ -20,7 +20,7 @@
|
||||
- block:
|
||||
- name: Get domain name from hostname.
|
||||
set_fact:
|
||||
ipaserver_domain: "{{ ansible_facts['hostname'].split('.')[0][1:] }}"
|
||||
ipaserver_domain: "{{ ansible_facts['fqdn'].split('.')[1:] | join('.') }}"
|
||||
rescue:
|
||||
- name: Fallback to 'ipa.test'
|
||||
set_fact:
|
||||
|
||||
@@ -24,11 +24,12 @@ import functools
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
from utils import get_test_playbooks, get_server_host, run_playbook
|
||||
from utils import get_test_playbooks, get_skip_conditions, run_playbook
|
||||
|
||||
|
||||
def prepare_test(test_name, test_path):
|
||||
"""Decorator for the tests generated automatically from playbooks.
|
||||
def prepare_test(testname, testpath):
|
||||
"""
|
||||
Decorate tests generated automatically from playbooks.
|
||||
|
||||
Injects 2 arguments to the test (`test_path` and `test_name`) and
|
||||
name the test method using test name (to ensure test reports are useful).
|
||||
@@ -36,13 +37,13 @@ def prepare_test(test_name, test_path):
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
kwargs["test_path"] = test_path
|
||||
kwargs["test_name"] = test_name
|
||||
kwargs["test_path"] = testpath
|
||||
kwargs["test_name"] = testname
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
decorator.__name__ = test_name
|
||||
decorator.__name__ = testname
|
||||
return decorator
|
||||
|
||||
|
||||
@@ -50,18 +51,21 @@ def prepare_test(test_name, test_path):
|
||||
# test_* methods.
|
||||
for test_dir_name, playbooks_in_dir in get_test_playbooks().items():
|
||||
_tests = {}
|
||||
|
||||
for playbook in playbooks_in_dir:
|
||||
test_name = playbook["name"].replace("-", "_")
|
||||
test_path = playbook["path"]
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not get_server_host(),
|
||||
reason="Environment variable IPA_SERVER_HOST must be set",
|
||||
)
|
||||
skip = get_skip_conditions(test_dir_name, test_name) or {}
|
||||
|
||||
# pylint: disable=W0621,W0640,W0613
|
||||
@pytest.mark.skipif(**skip)
|
||||
@pytest.mark.playbook
|
||||
@prepare_test(test_name, test_path)
|
||||
def method(self, test_path, test_name):
|
||||
run_playbook(test_path)
|
||||
# pylint: enable=W0621,W0640,W0613
|
||||
|
||||
_tests[test_name] = method
|
||||
|
||||
globals()[test_dir_name] = type(test_dir_name, tuple([TestCase]), _tests,)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
import testinfra
|
||||
@@ -45,6 +46,68 @@ def get_server_host():
|
||||
return os.getenv("IPA_SERVER_HOST")
|
||||
|
||||
|
||||
def get_disabled_test(group_name, test_name):
|
||||
disabled_modules = [
|
||||
disabled.strip()
|
||||
for disabled in os.environ.get("IPA_DISABLED_MODULES", "").split(",")
|
||||
]
|
||||
disabled_tests = [
|
||||
disabled.strip()
|
||||
for disabled in os.environ.get("IPA_DISABLED_TESTS", "").split(",")
|
||||
if disabled.strip()
|
||||
]
|
||||
|
||||
if not any([disabled_modules, disabled_tests]):
|
||||
return False
|
||||
|
||||
return group_name in disabled_modules or test_name in disabled_tests
|
||||
|
||||
|
||||
def get_enabled_test(group_name, test_name):
|
||||
enabled_modules = [
|
||||
enabled.strip()
|
||||
for enabled in os.environ.get("IPA_ENABLED_MODULES", "").split(":")
|
||||
if enabled.strip()
|
||||
]
|
||||
enabled_tests = [
|
||||
enabled.strip()
|
||||
for enabled in os.environ.get("IPA_ENABLED_TESTS", "").split(":")
|
||||
if enabled.strip()
|
||||
]
|
||||
|
||||
if not any([enabled_modules, enabled_tests]):
|
||||
return True
|
||||
|
||||
group_enabled = group_name in enabled_modules
|
||||
test_enabled = test_name in enabled_tests
|
||||
|
||||
return group_enabled or test_enabled
|
||||
|
||||
|
||||
def get_skip_conditions(group_name, test_name):
|
||||
"""
|
||||
Check tests that need to be skipped.
|
||||
|
||||
The return is a dict containing `condition` and `reason`. For the test
|
||||
to be skipped, `condition` must be True, if it is `False`, the test is
|
||||
to be skipped. Although "reason" must be always provided, it can be
|
||||
`None` if `condition` is True.
|
||||
"""
|
||||
if not get_server_host():
|
||||
return {
|
||||
"condition": True,
|
||||
"reason": "Environment variable IPA_SERVER_HOST must be set",
|
||||
}
|
||||
|
||||
if not get_enabled_test(group_name, test_name):
|
||||
return {"condition": True, "reason": "Test not configured to run"}
|
||||
|
||||
if get_disabled_test(group_name, test_name):
|
||||
return {"condition": True, "reason": "Test configured to not run"}
|
||||
|
||||
return {"condition": False, "reason": "Test will run."}
|
||||
|
||||
|
||||
def get_inventory_content():
|
||||
"""Create the content of an inventory file for a test run."""
|
||||
ipa_server_host = get_server_host()
|
||||
@@ -112,6 +175,7 @@ def _run_playbook(playbook):
|
||||
inventory_file.name,
|
||||
playbook,
|
||||
]
|
||||
# pylint: disable=subprocess-run-check
|
||||
process = subprocess.run(
|
||||
cmd, cwd=SCRIPT_DIR, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
@@ -238,17 +302,23 @@ class AnsibleFreeIPATestCase(TestCase):
|
||||
host_connection_info, ssh_identity_file=ssh_identity_file,
|
||||
)
|
||||
|
||||
def run_playbook(self, playbook, allow_failures=False):
|
||||
@staticmethod
|
||||
def run_playbook(playbook, allow_failures=False):
|
||||
return run_playbook(playbook, allow_failures)
|
||||
|
||||
def run_playbook_with_exp_msg(self, playbook, expected_msg):
|
||||
result = self.run_playbook(playbook, allow_failures=True)
|
||||
@staticmethod
|
||||
def run_playbook_with_exp_msg(playbook, expected_msg):
|
||||
result = run_playbook(playbook, allow_failures=True)
|
||||
assert (
|
||||
expected_msg in result.stdout.decode("utf8")
|
||||
or
|
||||
expected_msg in result.stderr.decode("utf8")
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def __is_text_on_data(text, data):
|
||||
return re.search(text, data) is not None
|
||||
|
||||
def check_details(self, expected_output, cmd, extra_cmds=None):
|
||||
cmd = "ipa " + cmd
|
||||
if extra_cmds:
|
||||
@@ -257,10 +327,16 @@ class AnsibleFreeIPATestCase(TestCase):
|
||||
res = self.master.run(cmd)
|
||||
if res.rc != 0:
|
||||
for output in expected_output:
|
||||
assert output in res.stderr
|
||||
assert self.__is_text_on_data(output, res.stderr), (
|
||||
f"\n{'='*40}\nExpected: {output}\n{'='*40}\n"
|
||||
+ f"Output:\n{res.stderr}{'='*40}\n"
|
||||
)
|
||||
else:
|
||||
for output in expected_output:
|
||||
assert output in res.stdout
|
||||
assert self.__is_text_on_data(output, res.stdout), (
|
||||
f"\n{'='*40}\nExpected: {output}\n{'='*40}\n"
|
||||
+ f"Output:\n{res.stdout}{'='*40}\n"
|
||||
)
|
||||
kdestroy(self.master)
|
||||
|
||||
def check_notexists(self, members, cmd, extra_cmds=None):
|
||||
@@ -270,7 +346,10 @@ class AnsibleFreeIPATestCase(TestCase):
|
||||
kinit_admin(self.master)
|
||||
res = self.master.run(cmd)
|
||||
for member in members:
|
||||
assert member not in res.stdout
|
||||
assert not self.__is_text_on_data(member, res.stdout), (
|
||||
f"\n{'='*40}\nExpected: {member}\n{'='*40}\n"
|
||||
+ f"Output:\n{res.stdout}{'='*40}\n"
|
||||
)
|
||||
kdestroy(self.master)
|
||||
|
||||
def mark_xfail_using_ansible_freeipa_version(self, version, reason):
|
||||
|
||||
@@ -19,6 +19,7 @@ be givedn without the other one.
|
||||
Options:
|
||||
-a Add all files, no only files known to git repo
|
||||
-k Keep build directory
|
||||
-i Install the generated collection
|
||||
-h Print this help
|
||||
|
||||
EOF
|
||||
@@ -26,7 +27,8 @@ EOF
|
||||
|
||||
all=0
|
||||
keep=0
|
||||
while getopts "ahk" arg; do
|
||||
install=0
|
||||
while getopts "ahki" arg; do
|
||||
case $arg in
|
||||
a)
|
||||
all=1
|
||||
@@ -38,6 +40,9 @@ while getopts "ahk" arg; do
|
||||
k)
|
||||
keep=1
|
||||
;;
|
||||
i)
|
||||
install=1
|
||||
;;
|
||||
\?)
|
||||
echo
|
||||
usage
|
||||
@@ -70,7 +75,7 @@ if [ -z "$galaxy_version" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Builing galaxy release: ${namespace}-${collection}-${galaxy_version}"
|
||||
echo "Building collection: ${namespace}-${collection}-${galaxy_version}"
|
||||
|
||||
GALAXY_BUILD=".galaxy-build"
|
||||
|
||||
@@ -102,6 +107,11 @@ sed -i -e "s/name: .*/name: \"$collection\"/" galaxy.yml
|
||||
|
||||
find . -name "*~" -exec rm {} \;
|
||||
|
||||
|
||||
echo "Creating CHANGELOG.rst..."
|
||||
"$(dirname "$0")/changelog" --galaxy > CHANGELOG.rst
|
||||
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
|
||||
|
||||
(cd plugins/module_utils && {
|
||||
@@ -182,3 +192,8 @@ if [ $keep == 0 ]; then
|
||||
else
|
||||
echo "Keeping build dir $GALAXY_BUILD"
|
||||
fi
|
||||
|
||||
if [ $install == 1 ]; then
|
||||
echo "Installing collection ${namespace}-${collection}-${galaxy_version}.tar.gz ..."
|
||||
ansible-galaxy collection install "${namespace}-${collection}-${galaxy_version}.tar.gz" --force
|
||||
fi
|
||||
|
||||
218
utils/changelog
218
utils/changelog
@@ -25,48 +25,6 @@ import argparse
|
||||
import subprocess
|
||||
|
||||
|
||||
usage = "Usage: changelog [options] [<new version>]"
|
||||
parser = argparse.ArgumentParser(usage=usage)
|
||||
parser.add_argument("--tag", dest="tag",
|
||||
help="git tag")
|
||||
options, args = parser.parse_known_args()
|
||||
|
||||
if len(args) == 1:
|
||||
new_version = args[0]
|
||||
elif len(args) != 0:
|
||||
parser.error("new version is not set")
|
||||
else:
|
||||
new_version = None
|
||||
|
||||
if options.tag is None:
|
||||
tag = subprocess.check_output(
|
||||
"git describe --tags $(git rev-list --tags --max-count=1)",
|
||||
shell=True)
|
||||
options.tag = tag.decode("utf-8").strip()
|
||||
|
||||
version = options.tag[1:]
|
||||
|
||||
command = ["git", "log", "%s.." % options.tag]
|
||||
process = subprocess.run(command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
|
||||
if process.returncode != 0:
|
||||
print("git log failed: %s" % process.stderr.decode("utf8").split("\n")[0])
|
||||
sys.exit(1)
|
||||
|
||||
if new_version is not None:
|
||||
s = "ansible-freeipa-%s" % new_version
|
||||
print(s)
|
||||
print("=" * len(s))
|
||||
print()
|
||||
|
||||
commits = {}
|
||||
prs = {}
|
||||
authors = {}
|
||||
|
||||
lines = process.stdout.decode("utf-8").split("\n")
|
||||
|
||||
class Ref:
|
||||
def __init__(self, commit):
|
||||
self.commit = commit
|
||||
@@ -75,11 +33,11 @@ class Ref:
|
||||
def store(commits, prs, authors, commit, author, merge, msg):
|
||||
if commit is not None:
|
||||
if msg[0].startswith("Merge pull request #"):
|
||||
pr = int(msg[0].split()[3][1:])
|
||||
pr_ref = int(msg[0].split()[3][1:])
|
||||
if len(msg) > 1:
|
||||
prs[pr] = msg[1].strip()
|
||||
prs[pr_ref] = msg[1].strip()
|
||||
else:
|
||||
prs[pr] = Ref(merge)
|
||||
prs[pr_ref] = Ref(merge)
|
||||
else:
|
||||
commits[commit] = msg[0].strip()
|
||||
authors.setdefault(author, []).append(commit)
|
||||
@@ -93,57 +51,125 @@ def get_commit(commits, commit):
|
||||
return commit
|
||||
|
||||
|
||||
commit = None
|
||||
author = None
|
||||
merge = None
|
||||
msg = None
|
||||
for line in lines:
|
||||
line = line.rstrip()
|
||||
if line.startswith("commit "):
|
||||
def get_output(command):
|
||||
try:
|
||||
ret = subprocess.check_output(command, shell=True)
|
||||
ret = ret.decode("utf-8").strip()
|
||||
except subprocess.CalledProcessError:
|
||||
print("Command '%s' failed" % command)
|
||||
sys.exit(1)
|
||||
return ret
|
||||
|
||||
|
||||
def changelog(tag):
|
||||
prev_tag = None
|
||||
if tag is not None and tag != "":
|
||||
prev_tag = get_output(
|
||||
"git describe --tag --abbrev=0 --always '%s^'" % tag)
|
||||
else:
|
||||
tag = get_output("git describe --tags --abbrev=0 "
|
||||
"$(git rev-list --tags --max-count=1)")
|
||||
|
||||
version = tag[1:]
|
||||
|
||||
if prev_tag is not None:
|
||||
command = ["git", "log", "%s..%s" % (prev_tag, tag)]
|
||||
else:
|
||||
command = ["git", "log", "%s.." % tag]
|
||||
process = subprocess.run(command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
|
||||
if process.returncode != 0:
|
||||
print("git log failed: %s" %
|
||||
process.stderr.decode("utf8").split("\n")[0])
|
||||
sys.exit(1)
|
||||
|
||||
lines = process.stdout.decode("utf-8").split("\n")
|
||||
|
||||
commits = {}
|
||||
prs = {}
|
||||
authors = {}
|
||||
commit = None
|
||||
author = None
|
||||
merge = None
|
||||
msg = None
|
||||
for line in lines:
|
||||
line = line.rstrip()
|
||||
if line.startswith("commit "):
|
||||
store(commits, prs, authors, commit, author, merge, msg)
|
||||
author = None
|
||||
msg = []
|
||||
commit = line[7:]
|
||||
elif line.startswith(" "):
|
||||
msg.append(line[4:])
|
||||
else:
|
||||
try:
|
||||
key, value = line.split(":", 1)
|
||||
if key == "Author":
|
||||
author = value.split("<")[0].strip()
|
||||
elif key == "Merge":
|
||||
merge = value.split()[1].strip()
|
||||
# Ignore Date, ..
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Add final commit
|
||||
if commit:
|
||||
store(commits, prs, authors, commit, author, merge, msg)
|
||||
author = None
|
||||
msg = []
|
||||
commit = line[7:]
|
||||
elif line.startswith(" "):
|
||||
msg.append(line[4:])
|
||||
|
||||
if prev_tag is not None:
|
||||
line = "Changes for %s since %s" % (version, prev_tag[1:])
|
||||
else:
|
||||
try:
|
||||
key, value = line.split(":", 1)
|
||||
if key == "Author":
|
||||
author = value.split("<")[0].strip()
|
||||
elif key == "Merge":
|
||||
merge = value.split()[1].strip()
|
||||
# Ignore Date, ..
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Add final commit
|
||||
if commit:
|
||||
store(commits, prs, authors, commit, author, merge, msg)
|
||||
|
||||
s = "Changes since %s" % version
|
||||
print("%s" % s)
|
||||
print("-" * len(s))
|
||||
print()
|
||||
|
||||
prs_sorted = sorted(prs.keys(), reverse=True)
|
||||
for pr in prs_sorted:
|
||||
if isinstance(prs[pr], Ref):
|
||||
msg = get_commit(commits, prs[pr].commit)
|
||||
else:
|
||||
msg = prs[pr]
|
||||
print(" - %s (#%d)" % (msg, pr))
|
||||
print()
|
||||
|
||||
s = "Detailed changelog since %s by author" % version
|
||||
print("%s" % s)
|
||||
print("-" * len(s))
|
||||
print(" %d authors, %d commits" % (len(authors), len(commits)))
|
||||
print()
|
||||
|
||||
authors_sorted = sorted(authors.keys())
|
||||
for author in authors_sorted:
|
||||
print("%s (%d)\n" % (author, len(authors[author])))
|
||||
for commit in authors[author]:
|
||||
print(" - %s" % commits[commit])
|
||||
line = "Changes since %s" % version
|
||||
print("%s" % line)
|
||||
print("-" * len(line))
|
||||
print()
|
||||
|
||||
prs_sorted = sorted(prs.keys(), reverse=True)
|
||||
for pr_ref in prs_sorted:
|
||||
if isinstance(prs[pr_ref], Ref):
|
||||
msg = get_commit(commits, prs[pr_ref].commit)
|
||||
else:
|
||||
msg = prs[pr_ref]
|
||||
print(" - %s (#%d)" % (msg, pr_ref))
|
||||
print()
|
||||
|
||||
if prev_tag is not None:
|
||||
line = "Detailed changelog for %s since %s by author" % (version,
|
||||
prev_tag[1:])
|
||||
else:
|
||||
line = "Detailed changelog since %s by author" % version
|
||||
print("%s" % line)
|
||||
print("-" * len(line))
|
||||
print(" %d authors, %d commits" % (len(authors), len(commits)))
|
||||
print()
|
||||
|
||||
authors_sorted = sorted(authors.keys())
|
||||
for author in authors_sorted:
|
||||
print("%s (%d)\n" % (author, len(authors[author])))
|
||||
for commit in authors[author]:
|
||||
print(" - %s" % commits[commit])
|
||||
print()
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(usage="Usage: changelog [options]")
|
||||
parser.add_argument("--tag", dest="tag", help="git tag")
|
||||
parser.add_argument("--galaxy", dest="galaxy", action="store_true",
|
||||
help="Create changelog for galaxy")
|
||||
options, args = parser.parse_known_args()
|
||||
|
||||
if len(args) != 0:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
if options.galaxy:
|
||||
# Get latest tag
|
||||
tag = get_output("git describe --tag --abbrev=0")
|
||||
# get number of commits since latest tag
|
||||
count = get_output("git rev-list '%s'.. --count" % tag)
|
||||
if count != "0":
|
||||
changelog(None)
|
||||
changelog(tag)
|
||||
else:
|
||||
changelog(options.tag)
|
||||
|
||||
Reference in New Issue
Block a user