On Fri, 2011-12-02 at 09:27 -0500, Rob Crittenden wrote: > Simo Sorce wrote: > > Hello all, > > > > with this set of patches it is possible to allow constrained delegation > > of credentials so that a service can impersonate a user when
[..] > In the third patch in ipadb_get_delegation_acl() you can just fall > through to the return. Removed useless check. I also noticed I had added the prototype declaration for the new vtable function in the 2nd patch instead of the 3rd where it belongs by mistake. So I fixed that too. > I think the content of this e-mail should be added as a README to the > source tree. Ok, I dumped and adapted the email content into a README file and added it to the third patch. I also fixed the patch names as per policy. Simo. -- Simo Sorce * Red Hat, Inc * New York
>From 6361de2b8a08a2ca8787300bd4672af1c855a857 Mon Sep 17 00:00:00 2001 From: Simo Sorce <sso...@redhat.com> Date: Sun, 20 Nov 2011 17:04:05 -0500 Subject: [PATCH 1/3] ipa-kdb: Delegation ACL schema --- install/share/60basev3.ldif | 5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-) diff --git a/install/share/60basev3.ldif b/install/share/60basev3.ldif index f518541586b2df9ed08718098a7f170563aa4e1d..b31a3d4dd77be57adcc0d97d24e23ecf71025756 100644 --- a/install/share/60basev3.ldif +++ b/install/share/60basev3.ldif @@ -14,7 +14,12 @@ attributeTypes: (2.16.840.1.113730.3.8.11.7 NAME 'ipaNTProfilePath' DESC 'User P attributeTypes: (2.16.840.1.113730.3.8.11.8 NAME 'ipaNTHomeDirectory' DESC 'User Home Directory Path' EQUALITY caseIgnoreMatch OREDRING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v3' ) attributeTypes: (2.16.840.1.113730.3.8.11.9 NAME 'ipaNTHomeDirectoryDrive' DESC 'User Home Drive Letter' EQUALITY caseIgnoreMatch OREDRING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v3' ) attributeTypes: (2.16.840.1.113730.3.8.11.10 NAME 'ipaNTDomainGUID' DESC 'NT Domain GUID' EQUALITY caseIgnoreIA5Match OREDRING caseIgnoreIA5OrderingMatch SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE X-ORIGIN 'IPA v3' ) +attributeTypes: ( 2.16.840.1.113730.3.8.11.20 NAME 'memberPrincipal' DESC 'Principal names member of a groupOfPrincipals group' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA-v3') +attributeTypes: ( 2.16.840.1.113730.3.8.11.21 NAME 'ipaAllowToImpersonate' DESC 'Principals that can be impersonated' SUP distinguishedName X-ORIGIN 'IPA-v3') +attributeTypes: ( 2.16.840.1.113730.3.8.11.22 NAME 'ipaAllowedTarget' DESC 'Target principals alowed to get a ticket for' SUP distinguishedName X-ORIGIN 'IPA-v3') objectClasses: (2.16.840.1.113730.3.8.12.1 NAME 'ipaExternalGroup' SUP top STRUCTURAL MUST ( cn ) MAY ( ipaExternalMember $ memberOf $ description $ owner) X-ORIGIN 'IPA v3' ) objectClasses: (2.16.840.1.113730.3.8.12.2 NAME 'ipaNTUserAttrs' SUP top AUXILIARY MUST ( ipaNTSecurityIdentifier ) MAY ( ipaNTHash $ ipaNTLogonScript $ ipaNTProfilePath $ ipaNTHomeDirectory $ ipaNTHomeDirectoryDrive ) X-ORIGIN 'IPA v3' ) objectClasses: (2.16.840.1.113730.3.8.12.3 NAME 'ipaNTGroupAttrs' SUP top AUXILIARY MUST ( ipaNTSecurityIdentifier ) X-ORIGIN 'IPA v3' ) objectClasses: (2.16.840.1.113730.3.8.12.4 NAME 'ipaNTDomainAttrs' SUP top AUXILIARY MUST ( ipaNTSecurityIdentifier $ ipaNTFlatName $ ipaNTDomainGUID ) MAY ( ipaNTFallbackPrimaryGroup ) X-ORIGIN 'IPA v3' ) +objectClasses: (2.16.840.1.113730.3.8.12.6 NAME 'groupOfPrincipals' SUP top AUXILIARY MUST ( cn ) MAY ( memberPrincipal ) X-ORIGIN 'IPA v3' ) +objectClasses: (2.16.840.1.113730.3.8.12.7 NAME 'ipaKrb5DelegationACL' SUP groupOfPrincipals STRUCTURAL MAY ( ipaAllowToImpersonate $ ipaAllowedTarget ) X-ORIGIN 'IPA v3' ) -- 1.7.7.1
>From 3a15f52a95aae218f66d6aba06dfa1ce874aa855 Mon Sep 17 00:00:00 2001 From: Simo Sorce <sso...@redhat.com> Date: Sun, 20 Nov 2011 20:50:27 -0500 Subject: [PATCH 2/3] ipa-kdb: enhance deref searches Allow to deref more than one attribute. The attrs searched are the same for all deref attributes at this time. --- daemons/ipa-kdb/ipa_kdb.h | 7 +++++-- daemons/ipa-kdb/ipa_kdb_common.c | 34 +++++++++++++++++++++++++--------- daemons/ipa-kdb/ipa_kdb_mspac.c | 11 +++++++++-- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h index 8c907c448d0f497786f7b66fb4e17e6590d4cc29..33b74a28ab9a283d635b050e93ee3760c1a55ec6 100644 --- a/daemons/ipa-kdb/ipa_kdb.h +++ b/daemons/ipa-kdb/ipa_kdb.h @@ -123,8 +123,11 @@ krb5_error_code ipadb_simple_modify(struct ipadb_context *ipactx, krb5_error_code ipadb_simple_delete_val(struct ipadb_context *ipactx, char *dn, char *attr, char *value); krb5_error_code ipadb_deref_search(struct ipadb_context *ipactx, - char *entry_dn, char **entry_attrs, - char *deref_attr_name, char **deref_attrs, + char *base_dn, int scope, + char *filter, + char **entry_attrs, + char **deref_attr_names, + char **deref_attrs, LDAPMessage **res); int ipadb_ldap_attr_to_int(LDAP *lcontext, LDAPMessage *le, diff --git a/daemons/ipa-kdb/ipa_kdb_common.c b/daemons/ipa-kdb/ipa_kdb_common.c index d3e8e9c4cb5438cd9c918daf5a19fc736fc698cb..6f5ac1d74f04c03bccdb19187a34d07b9784fa59 100644 --- a/daemons/ipa-kdb/ipa_kdb_common.c +++ b/daemons/ipa-kdb/ipa_kdb_common.c @@ -265,24 +265,39 @@ done: } krb5_error_code ipadb_deref_search(struct ipadb_context *ipactx, - char *entry_dn, char **entry_attrs, - char *deref_attr_name, char **deref_attrs, + char *base_dn, int scope, + char *filter, + char **entry_attrs, + char **deref_attr_names, + char **deref_attrs, LDAPMessage **res) { struct berval derefval = { 0, NULL }; LDAPControl *ctrl[2] = { NULL, NULL }; - LDAPDerefSpec ds[2]; + LDAPDerefSpec *ds; krb5_error_code kerr; int times; int ret; + int c; - ds[0].derefAttr = deref_attr_name; - ds[0].attributes = deref_attrs; - ds[1].derefAttr = NULL; + for (c = 0; deref_attr_names[c]; c++) { + /* count */ ; + } + + ds = calloc(c, sizeof(LDAPDerefSpec)); + if (!ds) { + return ENOMEM; + } + + for (c = 0; deref_attr_names[c]; c++) { + ds[c].derefAttr = deref_attr_names[c]; + ds[c].attributes = deref_attrs; + } ret = ldap_create_deref_control_value(ipactx->lcontext, ds, &derefval); if (ret != LDAP_SUCCESS) { - return ENOMEM; + kerr = ENOMEM; + goto done; } ret = ldap_control_create(LDAP_CONTROL_X_DEREF, @@ -297,8 +312,8 @@ krb5_error_code ipadb_deref_search(struct ipadb_context *ipactx, ret = LDAP_SUCCESS; while (!ipadb_need_retry(ipactx, ret) && times > 0) { times--; - ret = ldap_search_ext_s(ipactx->lcontext, entry_dn, - LDAP_SCOPE_BASE, "(objectclass=*)", + ret = ldap_search_ext_s(ipactx->lcontext, base_dn, + scope, filter, entry_attrs, 0, ctrl, NULL, &std_timeout, LDAP_NO_LIMIT, @@ -309,6 +324,7 @@ krb5_error_code ipadb_deref_search(struct ipadb_context *ipactx, done: ldap_memfree(derefval.bv_val); + free(ds); return kerr; } diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c index b435efeec0215d50694eeb668c0286b80056016f..160974ceb9cede21a3709316551fa5e1f1c5d5df 100644 --- a/daemons/ipa-kdb/ipa_kdb_mspac.c +++ b/daemons/ipa-kdb/ipa_kdb_mspac.c @@ -84,6 +84,11 @@ static char *user_pac_attrs[] = { NULL }; +char *deref_search_attrs[] = { + "memberOf", + NULL +}; + static char *memberof_pac_attrs[] = { "gidNumber", "ipaNTSecurityIdentifier", @@ -502,8 +507,10 @@ static krb5_error_code ipadb_get_pac(krb5_context kcontext, /* == Search PAC info == */ - kerr = ipadb_deref_search(ipactx, ied->entry_dn, user_pac_attrs, - "memberOf", memberof_pac_attrs, &results); + kerr = ipadb_deref_search(ipactx, ied->entry_dn, LDAP_SCOPE_BASE, + "(objectclass=*)", user_pac_attrs, + deref_search_attrs, memberof_pac_attrs, + &results); if (kerr) { goto done; } -- 1.7.7.1
>From f1378c4cebb88e61e3ccc1ab68ee2bbedd9fd6da Mon Sep 17 00:00:00 2001 From: Simo Sorce <sso...@redhat.com> Date: Sun, 20 Nov 2011 18:36:26 -0500 Subject: [PATCH] ipa-kdb: Add delgation access control support --- daemons/ipa-kdb/Makefile.am | 1 + daemons/ipa-kdb/README.s4u2proxy.txt | 124 ++++++++++++++++++++ daemons/ipa-kdb/ipa_kdb.c | 2 +- daemons/ipa-kdb/ipa_kdb.h | 7 + daemons/ipa-kdb/ipa_kdb_delegation.c | 209 ++++++++++++++++++++++++++++++++++ 5 files changed, 342 insertions(+), 1 deletions(-) create mode 100644 daemons/ipa-kdb/README.s4u2proxy.txt create mode 100644 daemons/ipa-kdb/ipa_kdb_delegation.c diff --git a/daemons/ipa-kdb/Makefile.am b/daemons/ipa-kdb/Makefile.am index c0a579bf4c74ac75a8da5d3f2dd774724e786bc8..77b92e8db865eb4bcc8ced50e0ef0352b6ea334f 100644 --- a/daemons/ipa-kdb/Makefile.am +++ b/daemons/ipa-kdb/Makefile.am @@ -34,6 +34,7 @@ ipadb_la_SOURCES = \ ipa_kdb_principals.c \ ipa_kdb_pwdpolicy.c \ ipa_kdb_mspac.c \ + ipa_kdb_delegation.c \ $(KRB5_UTIL_SRCS) \ $(NULL) diff --git a/daemons/ipa-kdb/README.s4u2proxy.txt b/daemons/ipa-kdb/README.s4u2proxy.txt new file mode 100644 index 0000000000000000000000000000000000000000..92d71bbd3e44c7a0ce97eb1443ce49c3b2f5e447 --- /dev/null +++ b/daemons/ipa-kdb/README.s4u2proxy.txt @@ -0,0 +1,124 @@ +It is now possible to allow constrained delegation of credentials so +that a service can impersonate a user when communicating with another +service w/o requiring the user to actually forward their TGT. +This makes for a much better method of delegating credentials as it +prevents exposure of the short term secret of the user. + +I added a relatively simple access control method that allow the KDC to +decide exactly which services are allowed to impersonate which users +against other services. A simple grouping mechanism is used so that in +large environments, clusters and otherwise classes of services can be +much more easily managed. + +The grouping mechanism has been built so that lookup is highly optimized +and is basically reduced to a single search that uses the derefernce +control. Speed is very important in this case because KDC operations +time out very quickly and unless we add a caching layer in ipa-kdb we +must keep the number of searches down to avoid client timeouts. + +The grouping mechanism is very simple a groupOfPrincipals object is +introduced, this Auxiliary class have a single optional attribute called +memberPrincipal which is a string containing a principal name. + +A separate objectclass is also introduced called ipaKrb5DelegationACL, +it is a subclass of groupOfPrincipals and is a Structural class. + +It has 2 additional optional attributes: ipaAllowedTarget and +ipaAllowToImpersonate. They are both DNs. + +The memberPrincipal attribute in this class contains the list of +principals that are being considered proxies[1]. That is: the +principals of the services that want to impersonate client principals +against other services. + +The ipaAllowedToImpersonate must point to a groupOfPrincipal based +object that contains the list of client principals (normally these are +user principals) that can be impersonated by this service. +If the attribute is missing than the service is allowed to impersonate +*any* user. + +The ipaAllowedTarget DN must point to a groupOfPrincipal based object +that contains the list of service principals that the proxy service is +allowed target when impersonating users. A target must be specified in +order to allow a service to access it impersonating another principal. + + +At the moment no wildcarding is implemented so services have to be +explicitly listed in their respective groups. +I have some idea of adding wildcard support at least for the +ipaAllowedToImpersonate group in order to separate user principals by +REALM. So you can say all users of REALM1 can be impersonated by this +service but no users of REALM2. + +It is unclear how this wildcarding may be implemented, but it must be +simple to avoid potentially very expensive computations every time a +ticket for the target services is requested. + +I have briefly tested this patch by manually creating a few objects then +using the kvno command to test that I could get a ldap ticket just using +the HTTP credentials (in order to do this I had to allow also s4u2self +operations for the HTTP service, but this is *not* generally required +and it is *not* desired in the IPA framework implementation). + +This patchset does not contain any CLI or UI nor installation changes to +create ipaKrb5DelegationACL obujects. It is indeed yet unclear where we +want to store them (suggestions are welcome) and how/when we may want to +expose this mechanism through UI/CLI for general usage. + +The initial intended usage is to allow us to move away from using +forwarded TGTs in the IPA framework and instead use S4U2Proxy in order +to access the ldap service. In order to do this some changes will need +to be made in installation scripts and replica management scripts later. + +How to test: + +Create 2 objects like these: + +dn: cn=ipa-http-delegation,... +objectClass: ipaKrb5DelegationACL +objectClass: groupOfPrincipals +cn: ipa-http-delegation +memberPrincipal: HTTP/ipaserver.example....@example.com +ipaAllowedTarget: cn=ipa-ldap-delegation-targets,... + +dn: cn=ipa-ldap-delegation-targets,... +objectClass: groupOfPrincipals +cn: ipa-ldap-delegation-targets +memberPrincipal: ldap/ipaserver.example....@example.com + + +In order to test with kvno which pretend to do s4u2self too you will +need to allow the HTTP service to impersonate arbitrary users. + +This is done with: +kdamin.local +modprinc +ok_to_auth_as_delegate HTTP/ipaserver.example.com + +Then run kvno as follows: + +# Init credntials as HTTP +kinit -kt /etc/httpd/conf/ipa.keytab HTTP/ipaserver.example.com + +# Perform S4U2Self +kvno -U admin HTTP/ipaserver.example.com + +# Perform S4U2Proxy +kvno -k /etc/httpd/conf/ipa.keytab -U admin -P HTTP/ipaserver.example.com +ldap/ipaserver.example.com + + +If this works it means you successfully impersonated the admin user with +the HTTP service against the ldap service. + +Simo. + + +[1] +Note that here I use the term proxy in a different way than it is used in +the krb interfaces. It may seem a bit confusing but I think people will +understand it better this way. + +In this document 'client' connects to 'proxy' which impersonates 'client' +against 'service'. +In the Code/API the 'client' connects to 'server' which impersonates +'client' against 'proxy'. diff --git a/daemons/ipa-kdb/ipa_kdb.c b/daemons/ipa-kdb/ipa_kdb.c index 05ee18720a11fc6b8579fd00206d1cbb9d5a1a34..ca266d546b9529778eec1b27bc10e0150c952b1f 100644 --- a/daemons/ipa-kdb/ipa_kdb.c +++ b/daemons/ipa-kdb/ipa_kdb.c @@ -458,6 +458,6 @@ kdb_vftabl kdb_function_table = { NULL, /* check_policy_tgs */ NULL, /* audit_as_req */ NULL, /* refresh_config */ - NULL /* check_allowed_to_delegate */ + ipadb_check_allowed_to_delegate /* check_allowed_to_delegate */ }; diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h index 33b74a28ab9a283d635b050e93ee3760c1a55ec6..2531d03281e899152b9418b545dd7f1122cf61bb 100644 --- a/daemons/ipa-kdb/ipa_kdb.h +++ b/daemons/ipa-kdb/ipa_kdb.h @@ -223,3 +223,10 @@ krb5_error_code ipadb_sign_authdata(krb5_context context, krb5_authdata ***signed_auth_data); krb5_error_code ipadb_reinit_mspac(struct ipadb_context *ipactx); + +/* DELEGATION CHECKS */ + +krb5_error_code ipadb_check_allowed_to_delegate(krb5_context kcontext, + krb5_const_principal client, + const krb5_db_entry *server, + krb5_const_principal proxy); diff --git a/daemons/ipa-kdb/ipa_kdb_delegation.c b/daemons/ipa-kdb/ipa_kdb_delegation.c new file mode 100644 index 0000000000000000000000000000000000000000..428e214b7f6a5efff2797a76480746427d12d35e --- /dev/null +++ b/daemons/ipa-kdb/ipa_kdb_delegation.c @@ -0,0 +1,209 @@ +/* + * MIT Kerberos KDC database backend for FreeIPA + * + * Authors: Simo Sorce <sso...@redhat.com> + * + * Copyright (C) 2011 Simo Sorce, 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/>. + */ + +#include "ipa_kdb.h" + +static char *acl_attrs[] = { + "objectClass", + "memberPrincipal", + NULL +}; + +static char *search_attrs[] = { + "ipaAllowToImpersonate", + "ipaAllowedTarget", + NULL +}; + +static krb5_error_code ipadb_get_delegation_acl(krb5_context kcontext, + char *srv_principal, + LDAPMessage **results) +{ + struct ipadb_context *ipactx; + krb5_error_code kerr; + char *filter = NULL; + int ret; + + ipactx = ipadb_get_context(kcontext); + if (!ipactx) { + return KRB5_KDB_DBNOTINITED; + } + + ret = asprintf(&filter, + "(&(objectclass=ipaKrb5DelegationACL)" + "(memberPrincipal=%s))", srv_principal); + if (ret == -1) { + kerr = ENOMEM; + goto done; + } + + /* == Search ACL info == */ + kerr = ipadb_deref_search(ipactx, ipactx->base, + LDAP_SCOPE_SUBTREE, filter, acl_attrs, + search_attrs, acl_attrs, results); + +done: + free(filter); + return kerr; +} + +static bool ipadb_match_member(char *princ, LDAPDerefRes *dres) +{ + LDAPDerefVal *dval; + int i; + + for (dval = dres->attrVals; dval; dval = dval->next) { + if (strcasecmp(dval->type, "memberPrincipal") != 0) { + continue; + } + + for (i = 0; dval->vals[i].bv_val != NULL; i++) { + /* FIXME: use utf8 aware comparison ? */ + /* FIXME: support wildcards ? */ + if (strncasecmp(princ, dval->vals[i].bv_val, + dval->vals[i].bv_len) == 0) { + return true; + } + } + } + + return false; +} + +static krb5_error_code ipadb_match_acl(krb5_context kcontext, + LDAPMessage *results, + krb5_const_principal client, + krb5_const_principal target) +{ + struct ipadb_context *ipactx; + krb5_error_code kerr = ENOENT; + LDAPMessage *lentry; + LDAPDerefRes *deref_results; + LDAPDerefRes *dres; + char *client_princ = NULL; + char *target_princ = NULL; + bool client_missing; + bool client_found; + bool target_found; + int ret; + + ipactx = ipadb_get_context(kcontext); + if (!ipactx) { + return KRB5_KDB_DBNOTINITED; + } + + kerr = krb5_unparse_name(kcontext, client, &client_princ); + if (kerr != 0) { + goto done; + } + kerr = krb5_unparse_name(kcontext, target, &target_princ); + if (kerr != 0) { + goto done; + } + + lentry = ldap_first_entry(ipactx->lcontext, results); + if (!lentry) { + kerr = ENOENT; + goto done; + } + + while (lentry) { + /* both client and target must be found in the same ACI */ + client_missing = true; + client_found = false; + target_found = false; + + ret = ipadb_ldap_deref_results(ipactx->lcontext, lentry, + &deref_results); + switch (ret) { + case 0: + for (dres = deref_results; dres; dres = dres->next) { + if (strcasecmp(dres->derefAttr, "ipaAllowToImpersonate") == 0) { + /* NOTE: client_missing is used to signal that the + * attribute was completely missing. This signals that + * ANY client is allowed to be impersonated. + * This logic is valid only for clients, not for targets */ + client_missing = false; + client_found = ipadb_match_member(client_princ, dres); + } + if (strcasecmp(dres->derefAttr, "ipaAllowedTarget") == 0) { + target_found = ipadb_match_member(target_princ, dres); + } + } + + ldap_derefresponse_free(deref_results); + break; + case ENOENT: + break; + default: + kerr = ret; + goto done; + } + + if ((client_found == true || client_missing == true) && + target_found == true) { + kerr = 0; + goto done; + } + + lentry = ldap_next_entry(ipactx->lcontext, lentry); + } + +done: + krb5_free_unparsed_name(kcontext, client_princ); + krb5_free_unparsed_name(kcontext, target_princ); + return kerr; +} + +/* Ok terminology is confusing here so read carefully: + * here 'proxy' is the service for which 'server' wants a ticket on behalf of + * 'client' */ + +krb5_error_code ipadb_check_allowed_to_delegate(krb5_context kcontext, + krb5_const_principal client, + const krb5_db_entry *server, + krb5_const_principal proxy) +{ + krb5_error_code kerr; + char *srv_principal = NULL; + LDAPMessage *res = NULL; + + kerr = krb5_unparse_name(kcontext, server->princ, &srv_principal); + if (kerr) { + goto done; + } + + kerr = ipadb_get_delegation_acl(kcontext, srv_principal, &res); + if (kerr) { + goto done; + } + + kerr = ipadb_match_acl(kcontext, res, client, proxy); + if (kerr) { + goto done; + } + +done: + krb5_free_unparsed_name(kcontext, srv_principal); + ldap_msgfree(res); + return kerr; +} -- 1.7.7.1
_______________________________________________ Freeipa-devel mailing list Freeipa-devel@redhat.com https://www.redhat.com/mailman/listinfo/freeipa-devel