mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-22 23:23:30 -06:00
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:
parent
f15da10454
commit
dbebed2e3a
@ -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
|
||||
|
219
doc/designs/client-install-pkinit.md
Normal file
219
doc/designs/client-install-pkinit.md
Normal 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.
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
||||
|
100
ipatests/test_integration/test_pkinit_install.py
Normal file
100
ipatests/test_integration/test_pkinit_install.py
Normal 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}"],
|
||||
)
|
Loading…
Reference in New Issue
Block a user