mirror of
https://github.com/freeipa/ansible-freeipa.git
synced 2026-03-28 22:33:05 +00:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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,19 +0,0 @@
|
||||
---
|
||||
driver:
|
||||
name: docker
|
||||
platforms:
|
||||
- name: centos-8-build
|
||||
image: "centos:centos8"
|
||||
pre_build_image: true
|
||||
hostname: ipaserver.test.local
|
||||
dns_servers:
|
||||
- 8.8.8.8
|
||||
volumes:
|
||||
- /sys/fs/cgroup:/sys/fs/cgroup:ro
|
||||
command: /usr/sbin/init
|
||||
privileged: true
|
||||
provisioner:
|
||||
name: ansible
|
||||
playbooks:
|
||||
prepare: ../resources/playbooks/prepare-build.yml
|
||||
prerun: false
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
driver:
|
||||
name: docker
|
||||
platforms:
|
||||
- name: centos-8
|
||||
image: quay.io/ansible-freeipa/upstream-tests:centos-8
|
||||
pre_build_image: true
|
||||
hostname: ipaserver.test.local
|
||||
dns_servers:
|
||||
- 127.0.0.1
|
||||
volumes:
|
||||
- /sys/fs/cgroup:/sys/fs/cgroup:ro
|
||||
command: /usr/sbin/init
|
||||
privileged: true
|
||||
provisioner:
|
||||
name: ansible
|
||||
playbooks:
|
||||
prepare: ../resources/playbooks/prepare.yml
|
||||
prerun: false
|
||||
@@ -1 +1 @@
|
||||
centos-8
|
||||
fedora-latest
|
||||
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
|
||||
@@ -139,6 +139,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 +228,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 +261,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))
|
||||
@@ -701,6 +711,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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
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
|
||||
|
||||
@@ -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,54 @@ 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"
|
||||
|
||||
# CentoOS 9 Stream
|
||||
|
||||
- stage: c9s_Ansible_Core_2_11
|
||||
dependsOn: []
|
||||
@@ -90,16 +119,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 +157,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 +194,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,
|
||||
dnsforwardzone,
|
||||
# ipa_disabled_tests: >-
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,)
|
||||
|
||||
@@ -45,6 +45,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 +174,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,11 +301,13 @@ 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
|
||||
|
||||
@@ -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