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 <abokovoy@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
This commit is contained in:
Alexander Bokovoy 2019-09-06 12:51:32 +03:00 committed by Rob Crittenden
parent 9db6f65a85
commit c5f32165d6
9 changed files with 235 additions and 21 deletions

View File

@ -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

10
API.txt
View File

@ -3219,11 +3219,19 @@ output: Output('result', type=[<type 'bool'>])
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
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)

View File

@ -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);

View File

@ -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;
}

View File

@ -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:

View File

@ -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.

View File

@ -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 ) )

View File

@ -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))

View File

@ -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=(