From c5f32165d6105a48d9de85a8d29925b58beb9f91 Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Fri, 6 Sep 2019 12:51:32 +0300 Subject: [PATCH] Add Authentication Indicator Kerberos ticket policy options For the authentication indicators 'otp', 'radius', 'pkinit', and 'hardened', allow specifying maximum ticket life and maximum renewable age in Kerberos ticket policy. The policy extensions are now loaded when a Kerberos principal data is requested by the KDC and evaluated in AS_REQ KDC policy check. If one of the authentication indicators mentioned above is present in the AS_REQ, corresponding policy is applied to the ticket. Related: https://pagure.io/freeipa/issue/8001 Signed-off-by: Alexander Bokovoy Reviewed-By: Alexander Bokovoy Reviewed-By: Rob Crittenden Reviewed-By: Florence Blanc-Renaud --- ACI.txt | 4 +- API.txt | 10 ++- daemons/ipa-kdb/ipa_kdb.h | 14 ++++ daemons/ipa-kdb/ipa_kdb_kdcpolicy.c | 21 ++++-- daemons/ipa-kdb/ipa_kdb_principals.c | 68 ++++++++++++++++++ doc/designs/krb-ticket-policy.md | 25 ++++--- install/share/60kerberos.ldif | 6 +- ipaserver/plugins/krbtpolicy.py | 94 ++++++++++++++++++++++++- ipatests/test_xmlrpc/test_krbtpolicy.py | 14 ++++ 9 files changed, 235 insertions(+), 21 deletions(-) diff --git a/ACI.txt b/ACI.txt index 3dcf84fd6..541d4d2f3 100644 --- a/ACI.txt +++ b/ACI.txt @@ -185,9 +185,9 @@ aci: (targetattr = "cn || createtimestamp || entryusn || ipabaseid || ipabaserid dn: cn=views,cn=accounts,dc=ipa,dc=example aci: (targetattr = "cn || createtimestamp || description || entryusn || ipadomainresolutionorder || modifytimestamp || objectclass")(targetfilter = "(objectclass=nsContainer)")(version 3.0;acl "permission:System: Read ID Views";allow (compare,read,search) userdn = "ldap:///all";) dn: cn=IPA.EXAMPLE,cn=kerberos,dc=ipa,dc=example -aci: (targetattr = "createtimestamp || entryusn || krbdefaultencsalttypes || krbmaxrenewableage || krbmaxticketlife || krbsupportedencsalttypes || modifytimestamp || objectclass")(targetfilter = "(objectclass=krbticketpolicyaux)")(version 3.0;acl "permission:System: Read Default Kerberos Ticket Policy";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Default Kerberos Ticket Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";) +aci: (targetattr = "createtimestamp || entryusn || krbauthindmaxrenewableage || krbauthindmaxticketlife || krbdefaultencsalttypes || krbmaxrenewableage || krbmaxticketlife || krbsupportedencsalttypes || modifytimestamp || objectclass")(targetfilter = "(objectclass=krbticketpolicyaux)")(version 3.0;acl "permission:System: Read Default Kerberos Ticket Policy";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Default Kerberos Ticket Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=users,cn=accounts,dc=ipa,dc=example -aci: (targetattr = "krbmaxrenewableage || krbmaxticketlife")(targetfilter = "(objectclass=krbticketpolicyaux)")(version 3.0;acl "permission:System: Read User Kerberos Ticket Policy";allow (compare,read,search) groupdn = "ldap:///cn=System: Read User Kerberos Ticket Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";) +aci: (targetattr = "krbauthindmaxrenewableage || krbauthindmaxticketlife || krbmaxrenewableage || krbmaxticketlife")(targetfilter = "(objectclass=krbticketpolicyaux)")(version 3.0;acl "permission:System: Read User Kerberos Ticket Policy";allow (compare,read,search) groupdn = "ldap:///cn=System: Read User Kerberos Ticket Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=locations,cn=etc,dc=ipa,dc=example aci: (targetfilter = "(objectclass=ipaLocationObject)")(version 3.0;acl "permission:System: Add IPA Locations";allow (add) groupdn = "ldap:///cn=System: Add IPA Locations,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=locations,cn=etc,dc=ipa,dc=example diff --git a/API.txt b/API.txt index a919e3a63..58c080ebd 100644 --- a/API.txt +++ b/API.txt @@ -3219,11 +3219,19 @@ output: Output('result', type=[]) output: Output('summary', type=[, ]) output: PrimaryKey('value') command: krbtpolicy_mod/1 -args: 1,9,3 +args: 1,17,3 arg: Str('uid?', cli_name='user') option: Str('addattr*', cli_name='addattr') option: Flag('all', autofill=True, cli_name='all', default=False) option: Str('delattr*', cli_name='delattr') +option: Int('krbauthindmaxrenewableage_hardened?', autofill=False, cli_name='hardened_maxrenew') +option: Int('krbauthindmaxrenewableage_otp?', autofill=False, cli_name='otp_maxrenew') +option: Int('krbauthindmaxrenewableage_pkinit?', autofill=False, cli_name='pkinit_maxrenew') +option: Int('krbauthindmaxrenewableage_radius?', autofill=False, cli_name='radius_maxrenew') +option: Int('krbauthindmaxticketlife_hardened?', autofill=False, cli_name='hardened_maxlife') +option: Int('krbauthindmaxticketlife_otp?', autofill=False, cli_name='otp_maxlife') +option: Int('krbauthindmaxticketlife_pkinit?', autofill=False, cli_name='pkinit_maxlife') +option: Int('krbauthindmaxticketlife_radius?', autofill=False, cli_name='radius_maxlife') option: Int('krbmaxrenewableage?', autofill=False, cli_name='maxrenew') option: Int('krbmaxticketlife?', autofill=False, cli_name='maxlife') option: Flag('raw', autofill=True, cli_name='raw', default=False) diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h index 08c24ae64..7519f26e2 100644 --- a/daemons/ipa-kdb/ipa_kdb.h +++ b/daemons/ipa-kdb/ipa_kdb.h @@ -94,6 +94,14 @@ enum ipadb_user_auth { IPADB_USER_AUTH_HARDENED = 1 << 5, }; +enum ipadb_user_auth_idx { + IPADB_USER_AUTH_IDX_OTP = 0, + IPADB_USER_AUTH_IDX_RADIUS, + IPADB_USER_AUTH_IDX_PKINIT, + IPADB_USER_AUTH_IDX_HARDENED, + IPADB_USER_AUTH_IDX_MAX, +}; + struct ipadb_global_config { time_t last_update; bool disable_last_success; @@ -128,6 +136,11 @@ struct ipadb_context { struct ipadb_global_config config; }; +struct ipadb_e_pol_limits { + krb5_deltat max_life; + krb5_deltat max_renewable_life; +}; + #define IPA_E_DATA_MAGIC 0x0eda7a struct ipadb_e_data { int magic; @@ -142,6 +155,7 @@ struct ipadb_e_data { char **authz_data; bool has_tktpolaux; enum ipadb_user_auth user_auth; + struct ipadb_e_pol_limits pol_limits[IPADB_USER_AUTH_IDX_MAX]; }; struct ipadb_context *ipadb_get_context(krb5_context kcontext); diff --git a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c index 6b61f162e..0b8aa668f 100644 --- a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c +++ b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c @@ -21,14 +21,13 @@ ipa_kdcpolicy_check_as(krb5_context context, krb5_kdcpolicy_moddata moddata, krb5_error_code kerr; enum ipadb_user_auth ua; struct ipadb_e_data *ied; + struct ipadb_e_pol_limits *pol_limits = NULL; int valid_auth_indicators = 0; *status = NULL; *lifetime_out = 0; *renew_lifetime_out = 0; - krb5_klog_syslog(LOG_INFO, "IPA kdcpolicy: checking AS-REQ."); - ied = (struct ipadb_e_data *)client->e_data; if (ied == NULL || ied->magic != IPA_E_DATA_MAGIC) { /* e-data is not availble, getting user auth from LDAP */ @@ -63,18 +62,21 @@ ipa_kdcpolicy_check_as(krb5_context context, krb5_kdcpolicy_moddata moddata, *status = "OTP pre-authentication not allowed for this user."; return KRB5KDC_ERR_POLICY; } + pol_limits = &(ied->pol_limits[IPADB_USER_AUTH_IDX_OTP]); } else if (strcmp(auth_indicator, "radius") == 0) { valid_auth_indicators++; if (!(ua & IPADB_USER_AUTH_RADIUS)) { *status = "OTP pre-authentication not allowed for this user."; return KRB5KDC_ERR_POLICY; } + pol_limits = &(ied->pol_limits[IPADB_USER_AUTH_IDX_RADIUS]); } else if (strcmp(auth_indicator, "pkinit") == 0) { valid_auth_indicators++; if (!(ua & IPADB_USER_AUTH_PKINIT)) { *status = "PKINIT pre-authentication not allowed for this user."; return KRB5KDC_ERR_POLICY; } + pol_limits = &(ied->pol_limits[IPADB_USER_AUTH_IDX_PKINIT]); } else if (strcmp(auth_indicator, "hardened") == 0) { valid_auth_indicators++; /* Allow hardened even if only password pre-auth is allowed */ @@ -82,6 +84,7 @@ ipa_kdcpolicy_check_as(krb5_context context, krb5_kdcpolicy_moddata moddata, *status = "Password pre-authentication not not allowed for this user."; return KRB5KDC_ERR_POLICY; } + pol_limits = &(ied->pol_limits[IPADB_USER_AUTH_IDX_HARDENED]); } } @@ -94,6 +97,18 @@ ipa_kdcpolicy_check_as(krb5_context context, krb5_kdcpolicy_moddata moddata, } } + /* If there were policy limits associated with the authentication indicators, + * apply them */ + if (pol_limits != NULL) { + if (pol_limits->max_life != 0) { + *lifetime_out = pol_limits->max_life; + } + + if (pol_limits->max_renewable_life != 0) { + *renew_lifetime_out = pol_limits->max_renewable_life; + } + } + return 0; } @@ -110,8 +125,6 @@ ipa_kdcpolicy_check_tgs(krb5_context context, krb5_kdcpolicy_moddata moddata, *lifetime_out = 0; *renew_lifetime_out = 0; - krb5_klog_syslog(LOG_INFO, "IPA kdcpolicy: checking TGS-REQ."); - return 0; } diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c index b208d090a..73085e813 100644 --- a/daemons/ipa-kdb/ipa_kdb_principals.c +++ b/daemons/ipa-kdb/ipa_kdb_principals.c @@ -79,6 +79,8 @@ static char *std_principal_attrs[] = { IPA_KRB_AUTHZ_DATA_ATTR, IPA_USER_AUTH_TYPE, "ipatokenRadiusConfigLink", + "krbAuthIndMaxTicketLife", + "krbAuthIndMaxRenewableAge", "objectClass", NULL @@ -88,6 +90,8 @@ static char *std_tktpolicy_attrs[] = { "krbmaxticketlife", "krbmaxrenewableage", "krbticketflags", + "krbauthindmaxticketlife", + "krbauthindmaxrenewableage", NULL }; @@ -506,6 +510,66 @@ cleanup: return ret; } +static void ipadb_parse_authind_policies(krb5_context kcontext, + LDAP *lcontext, + LDAPMessage *lentry, + krb5_db_entry *entry, + enum ipadb_user_auth ua) +{ + int result; + int ret; + struct ipadb_e_data *ied; + const struct { + char *attribute; + enum ipadb_user_auth flag; + enum ipadb_user_auth_idx idx; + } life_authind_map[] = { + {"krbAuthIndMaxTicketLife;otp", + IPADB_USER_AUTH_OTP, IPADB_USER_AUTH_IDX_OTP}, + {"krbAuthIndMaxTicketLife;radius", + IPADB_USER_AUTH_RADIUS, IPADB_USER_AUTH_IDX_RADIUS}, + {"krbAuthIndMaxTicketLife;pkinit", + IPADB_USER_AUTH_PKINIT, IPADB_USER_AUTH_IDX_PKINIT}, + {"krbAuthIndMaxTicketLife;hardened", + IPADB_USER_AUTH_HARDENED, IPADB_USER_AUTH_IDX_HARDENED}, + {NULL, IPADB_USER_AUTH_NONE, IPADB_USER_AUTH_IDX_MAX}, + }, age_authind_map[] = { + {"krbAuthIndMaxRenewableAge;otp", + IPADB_USER_AUTH_OTP, IPADB_USER_AUTH_IDX_OTP}, + {"krbAuthIndMaxRenewableAge;radius", + IPADB_USER_AUTH_RADIUS, IPADB_USER_AUTH_IDX_RADIUS}, + {"krbAuthIndMaxRenewableAge;pkinit", + IPADB_USER_AUTH_PKINIT, IPADB_USER_AUTH_IDX_PKINIT}, + {"krbAuthIndMaxRenewableAge;hardened", + IPADB_USER_AUTH_HARDENED, IPADB_USER_AUTH_IDX_HARDENED}, + {NULL, IPADB_USER_AUTH_NONE, IPADB_USER_AUTH_IDX_MAX}, + }; + + ied = (struct ipadb_e_data *)entry->e_data; + if (ied == NULL) { + return; + } + + for (size_t i = 0; life_authind_map[i].attribute != NULL; i++) { + if (ua & life_authind_map[i].flag) { + ret = ipadb_ldap_attr_to_int(lcontext, lentry, + life_authind_map[i].attribute, + &result); + if (ret == 0) { + ied->pol_limits[life_authind_map[i].idx].max_life = result; + } + + ret = ipadb_ldap_attr_to_int(lcontext, lentry, + age_authind_map[i].attribute, + &result); + if (ret == 0) { + ied->pol_limits[age_authind_map[i].idx].max_renewable_life = result; + } + } + } +} + + static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext, char *principal, LDAPMessage *lentry, @@ -861,6 +925,10 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext, goto done; } + if (ua & ~IPADB_USER_AUTH_NONE) { + ipadb_parse_authind_policies(kcontext, lcontext, lentry, entry, ua); + } + kerr = 0; done: diff --git a/doc/designs/krb-ticket-policy.md b/doc/designs/krb-ticket-policy.md index 6346d775b..096104413 100644 --- a/doc/designs/krb-ticket-policy.md +++ b/doc/designs/krb-ticket-policy.md @@ -71,19 +71,17 @@ Tickets without an authentication indicator will have the default lifetime / ren Administrators can specify max life and renew for each auth indicator and global default via `ipa krbtpolicy-mod` command. -e.g. `ipa krbtpolicy-mod --setattr otp-maxlife=604800 pkinit-maxtime=604800` +e.g. `ipa krbtpolicy-mod --otp-maxlife=604800 --pkinit-maxlife=604800` Current `--maxlife` and `--maxrenew` options for `ipa krbtpolicy-mod` will set the default max life / renew respectively. After this, the output for `ipa krbtpolicy-show` will look like: ``` -Max life: - - default: 86400 - - otp: 604800 - - pkinit: 604800 -Max renew: - - default: 604800 +Max life: 86400 +OTP max life: 604800 +PKINIT max life: 604800 +Max renew: 604800 ``` #### WebUI Workflow: @@ -110,7 +108,14 @@ but ticket lifecycle policy will require LDAP to store relations between authent and lifetime information. We have global ticket lifetime and renew time setting stored as attribute `krbmaxticketlife` and `krbmaxrenewableage` inside the `cn=$REALM,cn=kerberos,$SUFFIX` subtree, which represents the default lifetime policy. -For each authentication indicator, there will be an attribute to store specific lifetime for that indicator, -such as `krbmaxticketlifetop`, `krbmaxrenewableagepkinit` -They are stored in the same location as default policy in LDAP. +Two new multi-valued attributes are added to store an authentication +indicator-specific maximum ticket life and ticket's maximum renewable age. The +type of authentication indicator is specified as LDAP attribute option: + +``` +krbAuthIndMaxTicketLife;otp: 604800 +krbAuthIndMaxRenewableAge;pkinit: 604800 +``` + +They are stored in the same policy object in LDAP. diff --git a/install/share/60kerberos.ldif b/install/share/60kerberos.ldif index 45bea2aef..2ceae711e 100644 --- a/install/share/60kerberos.ldif +++ b/install/share/60kerberos.ldif @@ -269,6 +269,10 @@ attributetypes: ( 1.3.6.1.4.1.5322.21.2.4 NAME 'krbAllowedToDelegateTo' EQUALITY ##### A list of authentication indicator strings, one of which must be satisfied ##### to authenticate to the principal as a service. attributetypes: ( 2.16.840.1.113730.3.8.15.2.1 NAME 'krbPrincipalAuthInd' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) +##### The maximum ticket lifetime for a principal based on authentication indicator in seconds. Indicator is specified as an attribute option +attributetypes: ( 2.16.840.1.113730.3.8.15.2.2 NAME 'krbAuthIndMaxTicketLife' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27) +##### Maximum renewable lifetime for a principal's ticket based on authentication indicator in seconds. Indicator is specified as an attribute option +attributetypes: ( 2.16.840.1.113730.3.8.15.2.3 NAME 'krbAuthIndMaxRenewableAge' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27) ######################################################################## # Object Class Definitions # ######################################################################## @@ -312,6 +316,6 @@ objectClasses: ( 2.16.840.1.113719.1.301.6.13.1 NAME 'krbAdmService' SUP ( krbSe objectClasses: ( 2.16.840.1.113719.1.301.6.14.1 NAME 'krbPwdPolicy' SUP top MUST ( cn ) MAY ( krbMaxPwdLife $ krbMinPwdLife $ krbPwdMinDiffChars $ krbPwdMinLength $ krbPwdHistoryLength $ krbPwdMaxFailure $ krbPwdFailureCountInterval $ krbPwdLockoutDuration $ krbPwdAttributes $ krbPwdMaxLife $ krbPwdMaxRenewableLife $ krbPwdAllowedKeysalts ) ) ##### The krbTicketPolicyAux holds Kerberos ticket policy attributes. ##### This class can be attached to a principal object or realm object. -objectClasses: ( 2.16.840.1.113719.1.301.6.16.1 NAME 'krbTicketPolicyAux' AUXILIARY MAY ( krbTicketFlags $ krbMaxTicketLife $ krbMaxRenewableAge ) ) +objectClasses: ( 2.16.840.1.113719.1.301.6.16.1 NAME 'krbTicketPolicyAux' AUXILIARY MAY ( krbTicketFlags $ krbMaxTicketLife $ krbMaxRenewableAge $ krbAuthIndMaxTicketLife $ krbAuthIndMaxRenewableAge ) ) ##### The krbTicketPolicy object is an effective ticket policy that is associated with a realm or a principal objectClasses: ( 2.16.840.1.113719.1.301.6.17.1 NAME 'krbTicketPolicy' SUP top MUST ( cn ) ) diff --git a/ipaserver/plugins/krbtpolicy.py b/ipaserver/plugins/krbtpolicy.py index a66bbace3..997fe7e81 100644 --- a/ipaserver/plugins/krbtpolicy.py +++ b/ipaserver/plugins/krbtpolicy.py @@ -70,6 +70,10 @@ _default_values = { 'krbmaxrenewableage': 604800, } +# These attributes never have non-optional values, so they should be +# ignored in post callbacks +_option_based_attrs = ('krbauthindmaxticketlife', 'krbauthindmaxrenewableage') +_supported_options = ('otp', 'radius', 'pkinit', 'hardened') @register() class krbtpolicy(baseldap.LDAPObject): @@ -78,7 +82,9 @@ class krbtpolicy(baseldap.LDAPObject): """ container_dn = DN(('cn', api.env.realm), ('cn', 'kerberos')) object_name = _('kerberos ticket policy settings') - default_attributes = ['krbmaxticketlife', 'krbmaxrenewableage'] + default_attributes = ['krbmaxticketlife', 'krbmaxrenewableage', + 'krbauthindmaxticketlife', + 'krbauthindmaxrenewableage'] limit_object_classes = ['krbticketpolicyaux'] # permission_filter_objectclasses is deliberately missing, # so it is not possible to create a permission of `--type krbtpolicy`. @@ -94,7 +100,8 @@ class krbtpolicy(baseldap.LDAPObject): 'ipapermdefaultattr': { 'krbdefaultencsalttypes', 'krbmaxrenewableage', 'krbmaxticketlife', 'krbsupportedencsalttypes', - 'objectclass', + 'objectclass', 'krbauthindmaxticketlife', + 'krbauthindmaxrenewableage', }, 'default_privileges': { 'Kerberos Ticket Policy Readers', @@ -108,6 +115,7 @@ class krbtpolicy(baseldap.LDAPObject): 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'krbmaxrenewableage', 'krbmaxticketlife', + 'krbauthindmaxticketlife', 'krbauthindmaxrenewableage', }, 'default_privileges': { 'Kerberos Ticket Policy Readers', @@ -137,6 +145,46 @@ class krbtpolicy(baseldap.LDAPObject): doc=_('Maximum renewable age (seconds)'), minvalue=1, ), + Int('krbauthindmaxticketlife_otp?', + cli_name='otp_maxlife', + label=_('OTP max life'), + doc=_('OTP token maximum ticket life (seconds)'), + minvalue=1), + Int('krbauthindmaxrenewableage_otp?', + cli_name='otp_maxrenew', + label=_('OTP max renew'), + doc=_('OTP token ticket maximum renewable age (seconds)'), + minvalue=1), + Int('krbauthindmaxticketlife_radius?', + cli_name='radius_maxlife', + label=_('RADIUS max life'), + doc=_('RADIUS maximum ticket life (seconds)'), + minvalue=1), + Int('krbauthindmaxrenewableage_radius?', + cli_name='radius_maxrenew', + label=_('RADIUS max renew'), + doc=_('RADIUS ticket maximum renewable age (seconds)'), + minvalue=1), + Int('krbauthindmaxticketlife_pkinit?', + cli_name='pkinit_maxlife', + label=_('PKINIT max life'), + doc=_('PKINIT maximum ticket life (seconds)'), + minvalue=1), + Int('krbauthindmaxrenewableage_pkinit?', + cli_name='pkinit_maxrenew', + label=_('PKINIT max renew'), + doc=_('PKINIT ticket maximum renewable age (seconds)'), + minvalue=1), + Int('krbauthindmaxticketlife_hardened?', + cli_name='hardened_maxlife', + label=_('Hardened max life'), + doc=_('Hardened ticket maximum ticket life (seconds)'), + minvalue=1), + Int('krbauthindmaxrenewableage_hardened?', + cli_name='hardened_maxrenew', + label=_('Hardened max renew'), + doc=_('Hardened ticket maximum renewable age (seconds)'), + minvalue=1), ) def get_dn(self, *keys, **kwargs): @@ -145,6 +193,27 @@ class krbtpolicy(baseldap.LDAPObject): return DN(self.container_dn, api.env.basedn) +def rename_authind_options_from_ldap(entry_attrs, options): + if options.get('raw', False): + return + + for subtype in _supported_options: + for attr in _option_based_attrs: + name = '{};{}'.format(attr, subtype) + if name in entry_attrs: + new_name = '{}_{}'.format(attr, subtype) + entry_attrs[new_name] = entry_attrs.pop(name) + + +def rename_authind_options_to_ldap(entry_attrs): + for subtype in _supported_options: + for attr in _option_based_attrs: + name = '{}_{}'.format(attr, subtype) + if name in entry_attrs: + new_name = '{};{}'.format(attr, subtype) + entry_attrs[new_name] = entry_attrs.pop(name) + + @register() class krbtpolicy_mod(baseldap.LDAPUpdate): __doc__ = _('Modify Kerberos ticket policy.') @@ -158,6 +227,15 @@ class krbtpolicy_mod(baseldap.LDAPUpdate): # ticket policies are attached to objects with unrelated attributes if options.get('all'): options['all'] = False + + # Rename authentication indicator-specific policy elements to LDAP + rename_authind_options_to_ldap(entry_attrs) + return dn + + def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) + # Rename authentication indicator-specific policy elements from LDAP + rename_authind_options_from_ldap(entry_attrs, options) return dn @@ -200,9 +278,18 @@ class krbtpolicy_show(baseldap.LDAPRetrieve): default_entry = {} if attrname in default_entry: entry[attrname] = default_entry[attrname] - if attrname not in entry: + elif attrname in _option_based_attrs: + # If default entry contains option-based default attrs, + # copy the options explicitly + attrs = [(a, a.split(';')[0]) for a in default_entry] + for a in attrs: + if a[1] == attrname and a[0] not in entry: + entry[a[0]] = default_entry[a[0]] + if attrname not in entry and attrname not in _option_based_attrs: raise errors.ACIError( info=_('Default ticket policy could not be read')) + # Rename authentication indicator-specific policy elements from LDAP + rename_authind_options_from_ldap(entry, options) return dn @@ -239,5 +326,6 @@ class krbtpolicy_reset(baseldap.LDAPQuery): entry_attrs = ldap.get_entry(dn, self.obj.default_attributes) entry_attrs = entry_to_dict(entry_attrs, **options) + rename_authind_options_from_ldap(entry_attrs, options) return dict(result=entry_attrs, value=pkey_to_value(uid, options)) diff --git a/ipatests/test_xmlrpc/test_krbtpolicy.py b/ipatests/test_xmlrpc/test_krbtpolicy.py index aab7b7820..929c6764b 100644 --- a/ipatests/test_xmlrpc/test_krbtpolicy.py +++ b/ipatests/test_xmlrpc/test_krbtpolicy.py @@ -117,6 +117,20 @@ class test_krbtpolicy(Declarative): ), + dict( + desc='Update user ticket policy for auth indicator pkinit', + command=('krbtpolicy_mod', [user1], + dict(krbauthindmaxticketlife_pkinit=3600)), + expected=dict( + value=user1, + summary=None, + result=dict( + krbmaxticketlife=[u'3600'], + krbauthindmaxticketlife_pkinit=[u'3600'], + ), + ), + ), + dict( desc='Try updating other user attribute', command=(