Add PKINIT support to ipa-client-install

The ``ipa-client-install`` command now supports PKINIT for client
enrollment. Existing X.509 client certificates can be used to
authenticate a host.

Also restart KRB5 KDC during ``ipa-certupdate`` so KDC picks up new CA
certificates for PKINIT.

*Requirements*

- The KDC must trust the CA chain of the client certificate.
- The client must be able to verify the KDC's PKINIT cert.
- The host entry must exist. This limitation may be removed in the
  future.
- A certmap rule must match the host certificate and map it to a single
  host entry.

*Example*

```
ipa-client-install \
    --pkinit-identity=FILE:/path/to/cert.pem,/path/to/key.pem \
    --pkinit-anchor=/path/to/kdc-ca-bundle.pem
```

Fixes: https://pagure.io/freeipa/issue/9271
Fixes: https://pagure.io/freeipa/issue/9269
Signed-off-by: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
This commit is contained in:
Christian Heimes 2022-11-14 15:57:00 +01:00 committed by Alexander Bokovoy
parent f15da10454
commit dbebed2e3a
15 changed files with 606 additions and 20 deletions

View File

@ -11,7 +11,7 @@ Configures a client machine to use IPA for authentication and identity services.
By default this configures SSSD to connect to an IPA server for authentication and authorization. Optionally one can instead configure PAM and NSS (Name Switching Service) to work with an IPA server over Kerberos and LDAP.
An authorized user is required to join a client machine to IPA. This can take the form of a kerberos principal or a one\-time password associated with the machine.
An authorized account is required to join a client machine to IPA. This can take the form of a kerberos principal, a one\-time password associated with the machine, or PKINIT identity associated with the machine.
This same tool is used to unconfigure IPA and attempts to return the machine to its previous state. Part of this process is to unenroll the host from the IPA server. Unenrollment consists of disabling the principal key on the IPA server so that it may be re\-enrolled. The machine principal in /etc/krb5.keytab (host/<fqdn>@REALM) is used to authenticate to the IPA server to unenroll itself. If this principal does not exist then unenrollment will fail and an administrator will need to disable the host principal (ipa host\-disable <fqdn>).
@ -229,6 +229,22 @@ first. When this option is not specified, \fBipa\-client\-install\fR will back
up SSSD config and create new one. The back up version will be restored during
uninstall.
.SS "PKINIT OPTIONS"
.TP
\fB\-\-pkinit\-identity=\fIDENTITY\fR
Identity string for PKINIT authentication to use to join the IPA realm,
for example 'FILE:/path/to/cert.pem,/path/to/key.pem'. See krb5.conf(5)
for more information. The option is mutually exclusive with
\fB\-\-password\fR and \fB\-\-keytab\fR.
.TP
\fB\-\-pkinit\-anchor\fR=\fIFILEDIR\fR
Trust anchors (root and intermediate CA certs) for PKINIT. \fIFILEDIR\fR is
either the absolute path to a PEM bundle (for example
'FILE:/etc/pki/tls/cert.pem') or to an OpenSSL hash directory (for example
'DIR:/etc/ssl/certs/'). The option can be used multiple times. PKINIT
requires the full trust chain of the Kerberos KDC server as well as the full
trust chain of the identity certificate.
.SS "UNINSTALL OPTIONS"
.TP
\fB\-\-uninstall\fR

View File

@ -0,0 +1,219 @@
# IPA client enrollment with PKINIT
This design document proposes PKINIT as an additional authentication
mechanism for `ipa-client-install`, ticket
[https://pagure.io/freeipa/issue/9271](https://pagure.io/freeipa/issue/9271).
## Overview
PKINIT is an authentication mechanism for Kerberos that uses X.509
certificates and private keys to authenticate Kerberos KDC server to client
and optionally clients to the server. Mutual authentication is almost the
same as mTLS with TLS server and client certificates in HTTPS.
PKINIT can be used instead of user/password or OTP to authorize client
enrollment. This enables admins to re-use existing host certificates to
automate client installations.
## Assumptions
- The client machine has access to a X.509 certificate / key pair that
uniquely identifies an account with necessary permissions to enroll the
host. The certificate does not have to contain special extensions for
PKINIT. It only requires an attribute that can be uniquely mapped to an
account, e.g. subject alternative name (SAN) dNSName maps to FQDN of
an existing host entry or RFC 822 name maps to an user account with
enrollment privileges.
- A certmap rule exists matches the certificate, and maps the unique
identifier to a single account like an existing host entry or user with
enrollment privileges.
- The certificate is signed by a CA chain which is known to and trusted
by the Kerberos KDC server. This can be accomplished by installing the
root CA and intermediate CAs as additional CA certificates to IPA
with `ipa-cacert-manage install` on one IPA server, then running
`ipa-certupdate` on every IPA servers.
- The CA chain of the certificate and the CA chain of the Kerberos KDC
server certificate are available on the host. On an IPA server, the file
`/var/lib/ipa-client/pki/kdc-ca-bundle.pem` typically contains all
necessary CAs.
How the certificates and CAs are generated and distributed and how host
entries are created is out of scope for this document. The certificates
and key can be provided as files or by a PKCS#11 provider like OpenSC
from a smart card or p11-kit proxy from a remote PKCS#11 source.
### Host self-enrollment
Host self-enrollment allows a host to enroll itself using a host-specific
certificate. The approach use the fact that a host account has the necessary
permissions to self-manage its host entry. A host entry must be pre-created
by a privileged account and a certmap rule must map a unique identifier to
a host entry.
For example a certmap rule like
```console
$ ipa certmaprule-add pkinit-host \
--matchrule '<ISSUER>CN=Certificate Authority,O=IPA.EXAMPLE' \
--maprule='(fqdn={subject_dns_name})'
```
allows an host with hostname `host.ipa.example` and a certificate with
properties like
```console
Issuer: O = HMSIDM.TEST, CN = Certificate Authority
Subject: O = HMSIDM.TEST, CN = host.ipa.example
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:host.ipa.example
```
to enroll itself with a command like
```console
$ ipa-client-install \
--pkinit-identity=FILE:/path/to/cert.pem,/path/to/key.pem \
--pkinit-anchor=FILE:/path/to/kdc-ca-bundle.pem \
...
```
A privileged account only has to pre-created its host entry with
```console
$ ipa host-add host.ipa.example
```
first.
### Privileged user account
A user account with e.g. `Host Administrators` privilege can be used to
create and enroll new hosts.
Example certmap rule:
```console
$ ipa certmaprule-add pkinit-user \
--matchrule '<ISSUER>CN=Certificate Authority,O=IPA.EXAMPLE' \
--maprule='(&(mail={subject_rfc822_name})(objectclass=inetorgperson))'
```
In this case you have to pass the principal name of the user account to
`ipa-client-install`:
```console
$ ipa-client-install \
--pkinit-identity=FILE:/path/to/cert.pem,/path/to/key.pem \
--pkinit-anchor=FILE:/path/to/kdc-ca-bundle.pem \
-p enrollmentuser
...
```
## New options for ipa-client-install
The `ipa-client-install` command is extended with two new options:
`--pkinit-identity=IDENTITY` specifies the PKINIT identity information. The
option is mutually exclusive with `--keytab` and `--password` option.
See [man krb5.conf(5)](https://web.mit.edu/kerberos/krb5-1.19/doc/admin/conf_files/krb5_conf.html#pkinit-options)
for more information. Possible values are
- `FILE:/path/to/cert.pem,/path/to/key.pem` or `FILE:/path/to/combined.pem`
for certificate and private key in PEM format.
- `PKCS12:/path/to/file.p12` for PKCS#12 file with a certificate and private
key.
- `PKCS11:...` to use a PKCS#11 provider
- `DIR:/path/to/directory` with `*.crt` and `*.key` files.
`--pkinit-anchor=FILEDIR` to load trust anchors (root and intermediate CA
certs) for KDC server and host identity. *FILE* is either an absolute path to
a PEM bundle (for example `FILE:/etc/pki/tls/cert.pem`) or to an OpenSSL hash
directory (for example `DIR:/etc/ssl/certs/`). The option can be used multiple
times to load trust anchors from several locations.
By default `ipa-client-install` attempts to authenticate with the host
principal `host/hostname@REALM`. Use `-p` option to authenticate as a
different account.
Example:
```console
$ ipa-client-install \
--pkinit-identity=FILE:/path/to/cert.pem,/path/to/key.pem \
--pkinit-anchor=FILE:/path/to/kdc-ca-bundle.pem \
...
```
## Testing
The `ipa certmap-match` command does not support hosts, yet. To test a
rule and certificate, you can run kinit:
```console
$ kinit \
-X X509_user_identity=FILE:/path/to/cert.pem,/path/to/key.pem \
-X X509_anchors=FILE:/path/to/kdc-ca-bundle.pem \
host/host.ipa.example
```
Set the environment variable `KRB5_CONFIG=/dev/stderr` for additional debug
information. On IPA servers the log file `/var/log/krb5kdc.log` contains
information about cert authentication and filters:
```console
Initializing IPA certauth plugin.
Doing certauth authorize for [host/host.ipa.example@IPA.EXAMPLE]
Got cert filter [(fqdn=host.ipa.example)]
PKINIT: freshness token received from host/host.ipa.example@IPA.EXAMPLE
```
The KDC caches certmap rules for 5 minutes. To test a new or modified certmap
rule immediately, the KDC must be restarted with the command
`systemctl restart krb5kdc.service`.
## cert map rules
See [SSSD: Certificate mapping and matching rules](https://sssd.io/design-pages/matching_and_mapping_certificates.html)
and [man sss-certmap(5)](https://www.mankier.com/5/sss-certmap).
### certmap matching rule
certmap *matching rules* use RFC 4514 string representation of subject and
issuer distinguished names (DN) in LDAP order with NSS-style attribute type
names. RFC 4514 is the successor to RFC 2253. LDAP order is reverse to X.509
order of relative distinguised names, so common name (CN) typically comes
before organization and country.
- Matching rules are regular expressions. Characters `^.[$()|*+?{\\` must be
quoted, e.g. `.` becomes `\.`.
- Characters `=#+,;<=>\` have special meaning in in RDN attribute values
and may be escaped. A `,` becomes `\,`, which is then quoted again as
`\\,`.
- Some fields use different attribute type names than OpenSSL, e.g.
`emailAddress` attributes is just `E`.
To match a cert with issuer DN
`O = "ACME, Inc.", CN = host.acme.example, emailAddress = info@acme.example`,
use the matching rule
`<I>^E=info@acme.example,CN=host.acme.example,O=ACME\\, Inc\.$`.
### certmap mapping rules
certmap mapping rules are used to associate a certificate with an account.
Mapping rules are LDAP queries with templating, e.g.
`(fqdn={subject_dns_name})` uses the dNSName value from the certificate
subject alternative name extension.
- Combine the filter with `(!(krbprincipalkey=*))` to prevent further PKINIT
authenticate after the host has been enrolled:
`(&(fqdn={subject_dns_name})(!(krbprincipalkey=*)))`
- Use `(!(memberof=cn=ipaservers,cn=hostgroups,cn=accounts,.*))` to prevent
PKINIT as an IPA server host or
`(memberof=pkinit-hosts,cn=hostgroups,cn=accounts,.*)` to limit PKINIT to
hosts in a custom `pkinit-hosts` host group.

View File

@ -26,3 +26,4 @@ FreeIPA design documentation
external-idp/external-idp.md
external-idp/idp-api.md
random-serial-numbers.md
client-install-pkinit.md

View File

@ -40,7 +40,7 @@ from ipalib.constants import FQDN, IPAAPI_USER, MAXHOSTNAMELEN
from ipalib.install import certmonger, certstore, service
from ipalib.install import hostname as hostname_
from ipalib.facts import is_ipa_client_configured, is_ipa_configured
from ipalib.install.kinit import kinit_keytab, kinit_password
from ipalib.install.kinit import kinit_keytab, kinit_password, kinit_pkinit
from ipalib.install.service import enroll_only, prepare_only
from ipalib.rpc import delete_persistent_client_session_data
from ipalib.util import (
@ -2203,16 +2203,26 @@ def install_check(options):
pass
if options.unattended and (
options.password is None and
options.principal is None and
options.keytab is None and
options.prompt_password is False and
not options.on_master
options.password is None
and options.principal is None
and options.keytab is None
and options.pkinit_identity is None
and options.prompt_password is False
and not options.on_master
):
raise ScriptError(
"One of password / principal / keytab is required.",
rval=CLIENT_INSTALL_ERROR)
if options.pkinit_identity is not None and (
options.password is not None
or options.keytab is not None
):
raise ScriptError(
"pkinit_identity is mutually exclusive with password / keytab.",
rval=CLIENT_INSTALL_ERROR
)
if options.hostname:
hostname = options.hostname
hostname_source = 'Provided as option'
@ -2739,7 +2749,8 @@ def _install(options, tdict):
if not options.unattended:
if (options.principal is None and options.password is None and
options.prompt_password is False and options.keytab is None):
options.prompt_password is False and options.keytab is None
and options.pkinit_identity is None):
options.principal = user_input("User authorized to enroll "
"computers", allow_empty=False)
logger.debug(
@ -2773,6 +2784,7 @@ def _install(options, tdict):
env['XMLRPC_TRACE_CURL'] = 'yes'
if options.force_join:
join_args.append("-f")
if options.principal is not None:
stdin = None
principal = options.principal
@ -2832,6 +2844,33 @@ def _install(options, tdict):
"Keytab file could not be found: {}".format(
options.keytab),
rval=CLIENT_INSTALL_ERROR)
elif options.pkinit_identity:
join_args.append("-f")
with open(paths.CA_BUNDLE_PEM, "w"):
# HACK: kinit fails when "pkinit_pool" file is missing.
# Create an empty file and remove it after PKINIT.
pass
# if no principal is set, use host principal
if options.principal is None:
pkinit_principal = host_principal
else:
pkinit_principal = options.principal
try:
kinit_pkinit(
pkinit_principal,
options.pkinit_identity,
ccache_name=ccache_name,
config=krb_name,
pkinit_anchors=options.pkinit_anchors
)
except CalledProcessError as e:
print_port_conf_info()
raise ScriptError(
f"Kerberos PKINIT authentication failed: {e}",
rval=CLIENT_INSTALL_ERROR
)
finally:
remove_file(paths.CA_BUNDLE_PEM)
elif options.password:
nolog = (options.password,)
join_args.append("-w")
@ -3212,9 +3251,11 @@ def _install(options, tdict):
user = options.principal
if user is None:
user = "admin@%s" % cli_domain
logger.info("Principal is not set when enrolling with OTP"
"; using principal '%s' for 'getent passwd'",
user)
logger.info(
"Principal is not set when enrolling with OTP "
"or PKINIT; using principal '%s' for 'getent passwd'.",
user
)
elif '@' not in user:
user = "%s@%s" % (user, cli_domain)
n = 0
@ -3902,7 +3943,67 @@ class ClientInstallInterface(hostname_.HostNameInstallInterface,
"--all-ip-addresses")
@group
class PKINITInstallInterface(service.ServiceInstallInterface):
description = "PKINIT"
pkinit_identity = knob(
type=str,
default=None,
description=(
"PKINIT identity information (for example "
"FILE:/path/to/cert.pem,/path/to/key.pem)"
),
cli_metavar="IDENTITY",
)
@pkinit_identity.validator
def pkinit_identity(self, value):
# see pkinit_crypto_openssl.c:crypto_load_certs()
# ignore "ENV:" prefix
if not value.startswith(
("FILE:", "PKCS11:", "PKCS12:", "DIR:", "ENV:")
):
raise ValueError(
"identity must start with FILE:, PKCS11:, PKCS12:, DIR:, "
"or ENV:"
)
pkinit_anchors = knob(
type=typing.List[str],
default=None,
description=(
"PKINIT trust anchors, prefixed with FILE: for CA PEM bundle "
"file or DIR: for an OpenSSL hash dir. The option can be used "
"used multiple times."
),
cli_names="--pkinit-anchor",
cli_metavar="FILEDIR",
)
@pkinit_anchors.validator
def pkinit_anchors(self, value):
# see pkinit_crypto_openssl.c:crypto_load_cas_and_crls()
for part in value:
prefix, sep, path = part.partition(":")
if not sep or prefix not in {"FILE", "DIR", "ENV:"}:
raise ValueError(
"Invalid pkinit_anchor '{part}' is not prefixed with "
"FILE: or DIR:."
)
if prefix == "FILE" and not os.path.isfile(path):
raise ValueError(
f"pkinit anchor path '{path}' does not exist or is not "
"a file."
)
if prefix == "DIR" and not os.path.isdir(path):
raise ValueError(
f"pkinit anchor path '{path}' does not exist or is not "
"a file."
)
class ClientInstall(ClientInstallInterface,
PKINITInstallInterface,
automount.AutomountInstallInterface):
"""
Client installer

View File

@ -103,7 +103,9 @@ def run_with_args(api):
else:
lwcas = []
if is_ipa_configured():
ipa_configured = is_ipa_configured()
if ipa_configured:
# look up CA servers before service restarts
resp = api.Command.server_role_find(
role_servrole=u'CA server',
@ -141,6 +143,11 @@ def run_with_args(api):
update_client(certs)
# update_client() may have updated KDC cert bundle; restart KDC to pick
# up changes.
if ipa_configured and services.knownservices.krb5kdc.is_running():
services.knownservices.krb5kdc.restart()
def update_client(certs):
update_file(paths.IPA_CA_CRT, certs)

View File

@ -127,3 +127,49 @@ def kinit_armor(ccache_name, pkinit_anchors=None):
# this workaround enables us to capture stderr and put it
# into the raised exception in case of unsuccessful authentication
run(args, env=env, raiseonerr=True, capture_error=True)
def kinit_pkinit(
principal,
user_identity,
ccache_name,
config=None,
pkinit_anchors=None,
):
"""Perform kinit with X.509 identity (PKINIT)
:param principal: principal name
:param user_identity: X509_user_identity paramemter
:param ccache_name: location of ccache
:param config: path to krb5.conf (default: default location)
:param pkinit_anchor: if not None, the PKINIT anchors to use. Otherwise
the value from Kerberos client library configuration is used. Entries
must be prefixed with FILE: or DIR:
user identity example:
FILE:filename[,keyfilename]
PKCS12:filename
PKCS11:...
DIR:directoryname
:raises: CalledProcessError if PKINIT fails
"""
logger.debug(
"Initializing principal %s using PKINIT %s", principal, user_identity
)
env = {"LC_ALL": "C"}
if config is not None:
env["KRB5_CONFIG"] = config
args = [paths.KINIT, "-c", ccache_name]
if pkinit_anchors is not None:
for pkinit_anchor in pkinit_anchors:
assert pkinit_anchor.startswith(("FILE:", "DIR:", "ENV:"))
args.extend(["-X", f"X509_anchors={pkinit_anchor}"])
args.extend(["-X", f"X509_user_identity={user_identity}"])
args.append(principal)
# this workaround enables us to capture stderr and put it
# into the raised exception in case of unsuccessful authentication
run(args, env=env, raiseonerr=True, capture_error=True)

View File

@ -1462,6 +1462,18 @@ jobs:
timeout: 3600
topology: *master_1repl
fedora-latest/test_pkinit_install:
requires: [fedora-latest/build]
priority: 50
job:
class: RunPytest
args:
build_url: '{fedora-latest/build_url}'
test_suite: test_integration/test_pkinit_install.py
template: *ci-master-latest
timeout: 3600
topology: *master_1repl_1client
fedora-latest/test_pkinit_manage:
requires: [fedora-latest/build]
priority: 50

View File

@ -887,6 +887,20 @@ jobs:
timeout: 5400
topology: *master_1repl
pki-fedora/test_pkinit_install:
requires: [pki-fedora/build]
priority: 50
job:
class: RunPytest
args:
build_url: '{pki-fedora/build_url}'
update_packages: True
copr: '@pki/master'
test_suite: test_integration/test_pkinit_install.py
template: *ci-master-latest
timeout: 3600
topology: *master_1repl_1client
pki-fedora/test_pkinit_manage:
requires: [pki-fedora/build]
priority: 50

View File

@ -1577,6 +1577,19 @@ jobs:
timeout: 3600
topology: *master_1repl
fedora-latest/test_pkinit_install:
requires: [fedora-latest/build]
priority: 50
job:
class: RunPytest
args:
build_url: '{fedora-latest/build_url}'
selinux_enforcing: True
test_suite: test_integration/test_pkinit_install.py
template: *ci-master-latest
timeout: 3600
topology: *master_1repl_1client
fedora-latest/test_pkinit_manage:
requires: [fedora-latest/build]
priority: 50

View File

@ -1693,6 +1693,20 @@ jobs:
timeout: 3600
topology: *master_1repl
testing-fedora/test_pkinit_install:
requires: [testing-fedora/build]
priority: 50
job:
class: RunPytest
args:
build_url: '{testing-fedora/build_url}'
update_packages: True
enable_testing_repo: True
test_suite: test_integration/test_pkinit_install.py
template: *ci-master-latest
timeout: 3600
topology: *master_1repl_1client
testing-fedora/test_pkinit_manage:
requires: [testing-fedora/build]
priority: 50

View File

@ -1808,6 +1808,21 @@ jobs:
timeout: 3600
topology: *master_1repl
testing-fedora/test_pkinit_install:
requires: [testing-fedora/build]
priority: 50
job:
class: RunPytest
args:
build_url: '{testing-fedora/build_url}'
update_packages: True
selinux_enforcing: True
enable_testing_repo: True
test_suite: test_integration/test_pkinit_install.py
template: *ci-master-latest
timeout: 3600
topology: *master_1repl_1client
testing-fedora/test_pkinit_manage:
requires: [testing-fedora/build]
priority: 50

View File

@ -1462,6 +1462,18 @@ jobs:
timeout: 3600
topology: *master_1repl
fedora-previous/test_pkinit_install:
requires: [fedora-previous/build]
priority: 50
job:
class: RunPytest
args:
build_url: '{fedora-previous/build_url}'
test_suite: test_integration/test_pkinit_install.py
template: *ci-master-previous
timeout: 3600
topology: *master_1repl_1client
fedora-previous/test_pkinit_manage:
requires: [fedora-previous/build]
priority: 50

View File

@ -1577,6 +1577,19 @@ jobs:
timeout: 3600
topology: *master_1repl
fedora-rawhide/test_pkinit_install:
requires: [fedora-rawhide/build]
priority: 50
job:
class: RunPytest
args:
build_url: '{fedora-rawhide/build_url}'
update_packages: True
test_suite: test_integration/test_pkinit_install.py
template: *ci-master-frawhide
timeout: 3600
topology: *master_1repl_1client
fedora-rawhide/test_pkinit_manage:
requires: [fedora-rawhide/build]
priority: 50

View File

@ -533,8 +533,8 @@ def install_replica(master, replica, setup_ca=True, setup_dns=False,
def install_client(master, client, extra_args=[], user=None,
password=None, unattended=True, stdin_text=None,
nameservers='master'):
password=None, pkinit_identity=None, unattended=True,
stdin_text=None, nameservers='master'):
"""
:param nameservers: nameservers to write in resolver config. Possible
values:
@ -556,20 +556,23 @@ def install_client(master, client, extra_args=[], user=None,
if nameservers == 'master':
nameservers = master.ip
client.resolver.setup_resolver(nameservers, master.domain.name)
if user is None:
user = client.config.admin_name
if password is None:
password = client.config.admin_password
args = [
'ipa-client-install',
'--domain', client.domain.name,
'--realm', client.domain.realm,
'-p', user,
'-w', password,
'--server', master.hostname
]
if pkinit_identity:
args.extend(["--pkinit-identity", pkinit_identity])
else:
if user is None:
user = client.config.admin_name
if password is None:
password = client.config.admin_password
args.extend(['-p', user, '-w', password])
if unattended:
args.append('-U')

View File

@ -0,0 +1,100 @@
#
# Copyright (C) 2022 FreeIPA Contributors see COPYING for license
#
"""
Module provides tests for ipa-client-install with PKINIT
"""
import os
from ipaplatform.paths import paths
from ipatests.test_integration.base import IntegrationTest
from ipatests.pytest_ipa.integration import tasks
class TestPkinitClientInstall(IntegrationTest):
num_clients = 1
certfile = "/etc/pki/tls/certs/client.pem"
keyfile = "/etc/pki/tls/private/client.key"
tmpbundle = "/tmp/kdc-ca-bundle.pme"
@classmethod
def install(cls, mh):
tasks.install_master(cls.master)
def add_certmaperule(self):
"""add certmap rule to map SAN dNSName to host entry"""
self.master.run_command(
[
"ipa",
"certmaprule-add",
"pkinit-host",
"--matchrule=<ISSUER>CN=Certificate Authority,.*",
"--maprule=(fqdn={subject_dns_name})",
]
)
def add_host(self):
"""Add host entry for client
Allow master to manage client so it can create a certificate.
"""
client = self.clients[0]
self.master.run_command(
["ipa", "host-add", "--force", client.hostname]
)
self.master.run_command(
[
"ipa",
"host-add-managedby",
f"--hosts={self.master.hostname}",
client.hostname,
]
)
def create_cert(self):
"""Create and copy certificate for client"""
client = self.clients[0]
self.master.run_command(
[
"mkdir",
"-p",
os.path.dirname(self.certfile),
os.path.dirname(self.keyfile),
]
)
self.master.run_command(
[
"ipa-getcert",
"request",
"-w",
# fmt: off
"-f", self.certfile,
"-k", self.keyfile,
"-N", client.hostname,
"-D", client.hostname,
"-K", f"host/{client.hostname}",
# fmt: on
]
)
# copy cert, key, and bundle to client
for filename in (self.certfile, self.keyfile):
data = self.master.get_file_contents(filename)
client.put_file_contents(filename, data)
cabundle = self.master.get_file_contents(paths.KDC_CA_BUNDLE_PEM)
client.put_file_contents(self.tmpbundle, cabundle)
def test_client_install_pkinit(self):
tasks.kinit_admin(self.master)
self.add_certmaperule()
self.add_host()
self.create_cert()
tasks.install_client(
self.master,
self.clients[0],
pkinit_identity=f"FILE:{self.certfile},{self.keyfile}",
extra_args=[f"--pkinit-anchor=FILE:{self.tmpbundle}"],
)