Tolerate absence of PAC ticket signature depending of server capabilities

Since November 2020, Active Directory KDC generates a new type of
signature as part of the PAC. It is called "ticket signature", and is
generated based on the encrypted part of the ticket. The presence of
this signature is not mandatory in order for the PAC to be accepted for
S4U requests.

However, the behavior is different for MIT krb5. Support was added as
part of the 1.20 release, and this signature is required in order to
process S4U requests. Contrary to the PAC extended KDC signature, the
code generating this signature cannot be isolated and backported to
older krb5 versions because this version of the KDB API does not allow
passing the content of the ticket's encrypted part to IPA.

This is an issue in gradual upgrade scenarios where some IPA servers
rely on 1.19 and older versions of MIT krb5, while others use version
1.20 or newer. A service ticket that was provided by 1.19- IPA KDC will
be rejected when used by a service against a 1.20+ IPA KDC for S4U
requests.

On Fedora, CentOS 9 Stream, and RHEL 9, when the krb5 version is 1.20 or
newer, it will include a downstream-only update adding the
"optional_pac_tkt_chksum" KDB string attribute allowing to tolerate the
absence of PAC ticket signatures, if necessary.

This commit adds an extra step during the installation and update
processes where it adds a "pacTktSignSupported" ipaConfigString
attribute in "cn=KDC,cn=[server],cn=masters,cn=ipa,cn=etc,[basedn]" if
the MIT krb5 version IPA what built with was 1.20 or newer.

This commit also set "optional_pac_tkt_chksum" as a virtual KDB entry
attribute. This means the value of the attribute is not actually stored
in the database (to avoid race conditions), but its value is determined
at the KDC starting time by search the "pacTktSignSupported"
ipaConfigString in the server list. If this value is missing for at
least of them is missing, enforcement of the PAC ticket signature is
disabled by setting "optional_pac_tkt_chksum" to true for the local
realm TGS KDB entry.

For foreign realm TGS KDB entries, the "optional_pac_tkt_chksum" virtual
string attribute is set to true systematically, because, at least for
now, trusted AD domains can still have PAC ticket signature support
disabled.

Given the fact the "pacTktSignSupported" ipaConfigString for a single
server is added when this server is updated, and that the value of
"optional_pac_tkt_chksum" is determined at KDC starting time based on
the ipaConfigString attributes of all the KDCs in the domain, this
requires to restart all the KDCs in the domain after all IPA servers
were updated in order for PAC ticket signature enforcement to actually
take effect.

Fixes: https://pagure.io/freeipa/issue/9371
Signed-off-by: Julien Rische <jrische@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
This commit is contained in:
Julien Rische
2023-04-07 17:04:06 +02:00
committed by Florence Blanc-Renaud
parent 630cda5c06
commit bbe545ff9f
10 changed files with 221 additions and 24 deletions

View File

@@ -137,6 +137,11 @@ ifelse(IPA_VERSION_IS_GIT_SNAPSHOT, yes,
IPA_GIT_VERSION),
NEWLINE)) dnl IPA_VERSION end
########################################################
# Version of MIT krb5 used to build IPA
########################################################
define(IPA_KRB5_BUILD_VERSION, translit(esyscmd(krb5-config --version | awk '{ print $NF }'), NEWLINE))
dnl DEBUG: uncomment following lines and run command m4 VERSION.m4
dnl `IPA_VERSION: ''IPA_VERSION'
dnl `IPA_GIT_VERSION: ''IPA_GIT_VERSION'
@@ -144,3 +149,4 @@ dnl `IPA_GIT_BRANCH: ''IPA_GIT_BRANCH'
dnl `IPA_API_VERSION: ''IPA_API_VERSION'
dnl `IPA_DATA_VERSION: ''IPA_DATA_VERSION'
dnl `IPA_NUM_VERSION: ''IPA_NUM_VERSION'
dnl `IPA_KRB5_BUILD_VERSION: ''IPA_KRB5_BUILD_VERSION'

View File

@@ -460,6 +460,7 @@ AC_SUBST(VENDOR_SUFFIX)
AC_SUBST([VERSION], [IPA_VERSION])
AC_SUBST([GIT_VERSION], [IPA_GIT_VERSION])
AC_SUBST([GIT_BRANCH], [IPA_GIT_BRANCH])
AC_SUBST([KRB5_BUILD_VERSION], [IPA_KRB5_BUILD_VERSION])
# used by Makefile.am for files depending on templates
AC_SUBST([CONFIG_STATUS])

View File

@@ -524,6 +524,52 @@ static krb5_principal ipadb_create_local_tgs(krb5_context kcontext,
return tgtp;
}
static char *no_attrs[] = {
LDAP_NO_ATTRS,
NULL
};
static krb5_error_code
should_support_pac_tkt_sign(krb5_context kcontext, bool *result)
{
struct ipadb_context *ipactx;
krb5_error_code kerr;
LDAPMessage *res = NULL;
char *masters_dn = NULL;
int count;
char *kdc_filter = "(&(cn=KDC)(objectClass=ipaConfigObject)"
"(!(ipaConfigString=pacTktSignSupported)))";
ipactx = ipadb_get_context(kcontext);
if (!ipactx) {
kerr = KRB5_KDB_DBNOTINITED;
goto done;
}
count = asprintf(&masters_dn, "cn=masters,cn=ipa,cn=etc,%s", ipactx->base);
if (count < 0) {
kerr = ENOMEM;
goto done;
}
kerr = ipadb_simple_search(ipactx, masters_dn, LDAP_SCOPE_SUBTREE,
kdc_filter, no_attrs, &res);
if (kerr)
goto done;
count = ldap_count_entries(ipactx->lcontext, res);
if (result)
*result = (count == 0);
done:
free(masters_dn);
ldap_msgfree(res);
return kerr;
}
/* INTERFACE */
static krb5_error_code ipadb_init_library(void)
@@ -544,6 +590,7 @@ static krb5_error_code ipadb_init_module(krb5_context kcontext,
krb5_error_code kerr;
int ret;
int i;
bool pac_tkt_sign_supported;
/* make sure the context is freed to avoid leaking it */
ipactx = ipadb_get_context(kcontext);
@@ -628,6 +675,14 @@ static krb5_error_code ipadb_init_module(krb5_context kcontext,
goto fail;
}
/* Enforce PAC ticket signature verification if supported by all KDCs */
kerr = should_support_pac_tkt_sign(kcontext, &pac_tkt_sign_supported);
if (kerr) {
ret = kerr;
goto fail;
}
ipactx->optional_pac_tkt_chksum = !pac_tkt_sign_supported;
return 0;
fail:

View File

@@ -143,6 +143,7 @@ struct ipadb_context {
krb5_key_salt_tuple *def_encs;
int n_def_encs;
struct ipadb_mspac *mspac;
bool optional_pac_tkt_chksum;
#ifdef HAVE_KRB5_CERTAUTH_PLUGIN
krb5_certauth_moddata certauth_moddata;
#endif

View File

@@ -114,6 +114,8 @@ static char *std_principal_obj_classes[] = {
#define DEFAULT_TL_DATA_CONTENT "\x00\x00\x00\x00principal@UNINITIALIZED"
#define OPT_PAC_TKT_CHKSUM_STR_ATTR_NAME "optional_pac_tkt_chksum"
static int ipadb_ldap_attr_to_tl_data(LDAP *lcontext, LDAPMessage *le,
char *attrname,
krb5_tl_data **result, int *num)
@@ -179,6 +181,52 @@ done:
return ret;
}
static bool
is_tgs_princ(krb5_context kcontext, krb5_const_principal princ)
{
krb5_data *primary;
size_t l_tgs_name;
if (2 != krb5_princ_size(kcontext, princ))
return false;
primary = krb5_princ_component(kcontext, princ, 0);
l_tgs_name = strlen(KRB5_TGS_NAME);
if (l_tgs_name != primary->length)
return false;
return 0 == memcmp(primary->data, KRB5_TGS_NAME, l_tgs_name);
}
static krb5_error_code
cmp_local_tgs_princ(krb5_context kcontext, const char *local_realm,
krb5_const_principal princ, bool *result)
{
krb5_principal local_tgs_princ;
size_t l_local_realm;
krb5_error_code kerr;
bool res;
l_local_realm = strlen(local_realm);
kerr = krb5_build_principal(kcontext, &local_tgs_princ,
l_local_realm, local_realm,
KRB5_TGS_NAME, local_realm, NULL);
if (kerr)
goto end;
res = (bool) krb5_principal_compare(kcontext, local_tgs_princ, princ);
if (result)
*result = res;
end:
krb5_free_principal(kcontext, local_tgs_princ);
return kerr;
}
krb5_error_code ipadb_set_tl_data(krb5_db_entry *entry,
krb5_int16 type,
krb5_ui_2 length,
@@ -1658,6 +1706,8 @@ krb5_error_code ipadb_get_principal(krb5_context kcontext,
krb5_db_entry **entry)
{
struct ipadb_context *ipactx;
bool is_local_tgs_princ;
const char *opt_pac_tkt_chksum_val;
krb5_error_code kerr;
*entry = NULL;
@@ -1673,11 +1723,33 @@ krb5_error_code ipadb_get_principal(krb5_context kcontext,
/* Lookup local names and aliases first. */
kerr = dbget_princ(kcontext, ipactx, search_for, flags, entry);
if (kerr != KRB5_KDB_NOENTRY) {
if (kerr == KRB5_KDB_NOENTRY) {
kerr = dbget_alias(kcontext, ipactx, search_for, flags, entry);
}
if (kerr)
return kerr;
/* If TGS principal, some virtual attributes may be added */
if (is_tgs_princ(kcontext, (*entry)->princ)) {
kerr = cmp_local_tgs_princ(kcontext, ipactx->realm, (*entry)->princ,
&is_local_tgs_princ);
if (kerr)
return kerr;
/* PAC ticket signature should be optional for foreign realms, and local
* realm if not supported by all servers
*/
if (!is_local_tgs_princ || ipactx->optional_pac_tkt_chksum)
opt_pac_tkt_chksum_val = "true";
else
opt_pac_tkt_chksum_val = "false";
kerr = krb5_dbe_set_string(kcontext, *entry,
OPT_PAC_TKT_CHKSUM_STR_ATTR_NAME,
opt_pac_tkt_chksum_val);
}
return dbget_alias(kcontext, ipactx, search_for, flags, entry);
return kerr;
}
void ipadb_free_principal_e_data(krb5_context kcontext, krb5_octet *e_data)
@@ -1988,6 +2060,20 @@ done:
return kerr;
}
static bool should_filter_out_attr(krb5_tl_data *data)
{
switch (data->tl_data_type) {
case KRB5_TL_DB_ARGS:
case KRB5_TL_KADM_DATA:
case KRB5_TL_LAST_ADMIN_UNLOCK:
case KRB5_TL_LAST_PWD_CHANGE:
case KRB5_TL_MKVNO:
return true;
default:
return false;
}
}
static krb5_error_code ipadb_get_ldap_mod_extra_data(struct ipadb_mods *imods,
krb5_tl_data *tl_data,
int mod_op)
@@ -1999,13 +2085,8 @@ static krb5_error_code ipadb_get_ldap_mod_extra_data(struct ipadb_mods *imods,
int n, i;
for (n = 0, data = tl_data; data; data = data->tl_data_next) {
if (data->tl_data_type == KRB5_TL_LAST_PWD_CHANGE ||
data->tl_data_type == KRB5_TL_KADM_DATA ||
data->tl_data_type == KRB5_TL_DB_ARGS ||
data->tl_data_type == KRB5_TL_MKVNO ||
data->tl_data_type == KRB5_TL_LAST_ADMIN_UNLOCK) {
if (should_filter_out_attr(data))
continue;
}
n++;
}
@@ -2021,13 +2102,8 @@ static krb5_error_code ipadb_get_ldap_mod_extra_data(struct ipadb_mods *imods,
for (i = 0, data = tl_data; data; data = data->tl_data_next) {
if (data->tl_data_type == KRB5_TL_LAST_PWD_CHANGE ||
data->tl_data_type == KRB5_TL_KADM_DATA ||
data->tl_data_type == KRB5_TL_DB_ARGS ||
data->tl_data_type == KRB5_TL_MKVNO ||
data->tl_data_type == KRB5_TL_LAST_ADMIN_UNLOCK) {
if (should_filter_out_attr(data))
continue;
}
be_type = htons(data->tl_data_type);
@@ -2779,10 +2855,37 @@ done:
return kerr;
}
static krb5_error_code
remove_virtual_str_attrs(krb5_context kcontext, krb5_db_entry *entry)
{
char *str_attr_val;
krb5_error_code kerr;
kerr = krb5_dbe_get_string(kcontext, entry,
OPT_PAC_TKT_CHKSUM_STR_ATTR_NAME,
&str_attr_val);
if (kerr)
return kerr;
if (str_attr_val)
kerr = krb5_dbe_set_string(kcontext, entry,
OPT_PAC_TKT_CHKSUM_STR_ATTR_NAME,
NULL);
krb5_dbe_free_string(kcontext, str_attr_val);
return kerr;
}
krb5_error_code ipadb_put_principal(krb5_context kcontext,
krb5_db_entry *entry,
char **db_args)
{
krb5_error_code kerr;
kerr = remove_virtual_str_attrs(kcontext, entry);
if (kerr)
return kerr;
if (entry->mask & KMASK_PRINCIPAL) {
return ipadb_add_principal(kcontext, entry);
} else {

View File

@@ -13,11 +13,12 @@ bdist_wheel: version.py
$(AM_V_GEN)awk '$$1 == "default:" { print $$2 }' $< >$@
version.py: version.py.in .DEFAULT_PLUGINS $(top_builddir)/$(CONFIG_STATUS)
$(AM_V_GEN)sed \
-e 's|@API_VERSION[@]|$(API_VERSION)|g' \
-e 's|@NUM_VERSION[@]|$(NUM_VERSION)|g' \
-e 's|@VERSION[@]|$(VERSION)|g' \
-e 's|@VENDOR_SUFFIX[@]|$(VENDOR_SUFFIX)|g' \
-e '/@DEFAULT_PLUGINS[@]/r .DEFAULT_PLUGINS' \
-e '/@DEFAULT_PLUGINS[@]/d' \
$(AM_V_GEN)sed \
-e 's|@API_VERSION[@]|$(API_VERSION)|g' \
-e 's|@NUM_VERSION[@]|$(NUM_VERSION)|g' \
-e 's|@VERSION[@]|$(VERSION)|g' \
-e 's|@VENDOR_SUFFIX[@]|$(VENDOR_SUFFIX)|g' \
-e 's|@KRB5_BUILD_VERSION[@]|$(KRB5_BUILD_VERSION)|g' \
-e '/@DEFAULT_PLUGINS[@]/r .DEFAULT_PLUGINS' \
-e '/@DEFAULT_PLUGINS[@]/d' \
$< > $@

View File

@@ -17,6 +17,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from pkg_resources import parse_version
# The full version including strings
VERSION = "@VERSION@"
@@ -51,3 +53,5 @@ API_VERSION = "@API_VERSION@"
DEFAULT_PLUGINS = frozenset(l.strip() for l in """
@DEFAULT_PLUGINS@
""".strip().splitlines())
KRB5_BUILD_VERSION = parse_version("@KRB5_BUILD_VERSION@")

View File

@@ -26,6 +26,7 @@ import socket
import dbus
import dns.name
from pkg_resources import parse_version
from ipalib import x509
from ipalib.install import certstore
@@ -34,6 +35,7 @@ from ipaserver.install import installutils
from ipapython import ipaldap
from ipapython import ipautil
from ipapython import kernel_keyring
from ipapython.version import KRB5_BUILD_VERSION
from ipalib import api, errors
from ipalib.constants import ANON_USER
from ipalib.install import certmonger
@@ -42,15 +44,17 @@ from ipapython.dogtag import KDC_PROFILE
from ipaserver.install import replication
from ipaserver.install import certs
from ipaserver.masters import find_providing_servers
from ipaserver.masters import (
find_providing_servers,
PAC_TKT_SIGN_SUPPORTED,
PKINIT_ENABLED,
)
from ipaplatform.constants import constants
from ipaplatform.tasks import tasks
from ipaplatform.paths import paths
logger = logging.getLogger(__name__)
PKINIT_ENABLED = 'pkinitEnabled'
MASTER_KEY_TYPE = 'aes256-sha2'
SUPPORTED_ENCTYPES = ('aes256-sha2:special', 'aes128-sha2:special',
'aes256-sha2:normal', 'aes128-sha2:normal',
@@ -169,6 +173,13 @@ class KrbInstance(service.Service):
# Add the host to the ipaserver host group
self._ldap_update(['20-ipaservers_hostgroup.update'])
def pac_tkt_sign_support_enable(self):
"""
Advertise PAC ticket signature support in master's KDC entry in LDAP
"""
service.set_service_entry_config(
'KDC', self.fqdn, [PAC_TKT_SIGN_SUPPORTED], self.suffix)
def __common_setup(self, realm_name, host_name, domain_name, admin_password):
self.fqdn = host_name
self.realm = realm_name.upper()
@@ -212,6 +223,10 @@ class KrbInstance(service.Service):
self.__common_post_setup()
if KRB5_BUILD_VERSION >= parse_version('1.20'):
self.step("enable PAC ticket signature support",
self.pac_tkt_sign_support_enable)
self.start_creation()
self.kpasswd = KpasswdInstance()
@@ -235,6 +250,10 @@ class KrbInstance(service.Service):
self.__common_post_setup()
if KRB5_BUILD_VERSION >= parse_version('1.20'):
self.step("enable PAC ticket signature support",
self.pac_tkt_sign_support_enable)
self.start_creation()
self.kpasswd = KpasswdInstance()

View File

@@ -18,6 +18,7 @@ import sys
import tempfile
from contextlib import contextmanager
from augeas import Augeas
from pkg_resources import parse_version
from ipalib import api, x509
from ipalib.constants import RENEWAL_CA_NAME, RA_AGENT_PROFILE, IPA_CA_RECORD
@@ -36,6 +37,7 @@ from ipapython import ipautil, version
from ipapython import ipaldap
from ipapython import directivesetter
from ipapython.dn import DN
from ipapython.version import KRB5_BUILD_VERSION
from ipaplatform.constants import constants
from ipaplatform.paths import paths
from ipaserver import servroles
@@ -1961,6 +1963,9 @@ def upgrade_configuration():
enable_server_snippet()
setup_kpasswd_server(krb)
if KRB5_BUILD_VERSION >= parse_version('1.20'):
krb.pac_tkt_sign_support_enable()
# Must be executed after certificate_renewal_update
# (see function docstring for details)
http_certificate_ensure_ipa_ca_dnsname(http)

View File

@@ -20,6 +20,8 @@ logger = logging.getLogger(__name__)
CONFIGURED_SERVICE = u'configuredService'
ENABLED_SERVICE = u'enabledService'
HIDDEN_SERVICE = u'hiddenService'
PAC_TKT_SIGN_SUPPORTED = u'pacTktSignSupported'
PKINIT_ENABLED = u'pkinitEnabled'
# The service name as stored in cn=masters,cn=ipa,cn=etc. The values are:
# 0: systemd service name