New roles for smartcard server and client setup

There are new smartcard roles in the roles folder:

    roles/ipasmartcard_server
    roles/ipasmartcard_client

This roles allows to setup smartcard for servers and clients.

Here is the documentation for the roles:

    roles/ipasmartcard_server/README.md
    roles/ipasmartcard_client/README.md

New example playbooks have been added:

    playbooks/install-smartcard-server.yml
    playbooks/install-smartcard-replicas.yml
    playbooks/install-smartcard-servers.yml
    playbooks/install-smartcard-clients.yml
This commit is contained in:
Thomas Woerner
2022-06-13 10:37:32 +02:00
parent fdfea1b6fb
commit 9932b1dc98
26 changed files with 1482 additions and 0 deletions

View File

@@ -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
=========================

View File

@@ -0,0 +1,8 @@
---
- name: Playbook to setup smartcard for IPA clients
hosts: ipaclients
become: true
roles:
- role: ipasmartcard_client
state: present

View File

@@ -0,0 +1,8 @@
---
- name: Playbook to setup smartcard for IPA replicas
hosts: ipareplicas
become: true
roles:
- role: ipasmartcard_server
state: present

View File

@@ -0,0 +1,8 @@
---
- name: Playbook to setup smartcard for IPA server
hosts: ipaserver
become: true
roles:
- role: ipasmartcard_server
state: present

View 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

View 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

View File

@@ -0,0 +1,4 @@
---
# defaults file for ipasmartcard_client role
ipaclient_install_packages: yes

View File

@@ -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}"

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View 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

View 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

View File

@@ -0,0 +1,3 @@
---
ipasmartcard_client_remove_pam_pkcs11_packages: [ "pam_pkcs11" ]
ipasmartcard_client_packages: [ "opensc", "dconf", "krb5-pkinit-openssl" ]

View 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

View File

@@ -0,0 +1,4 @@
---
# defaults file for ipasmartcard_server role
ipaserver_install_packages: yes

View File

@@ -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}"

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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()

View File

@@ -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()

View 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

View 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

View File

@@ -0,0 +1,2 @@
---
ipasmartcard_server_bindutils_packages: [ "bind-utils" ]