]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
Add auto_fast_armor realm variable master 1534/head
authorjason34105533 <a20040108jason@gmail.com>
Thu, 23 Jul 2026 08:44:15 +0000 (16:44 +0800)
committerGreg Hudson <ghudson@mit.edu>
Fri, 7 Aug 2026 23:54:45 +0000 (19:54 -0400)
When auto_fast_armor is true and no FAST ccache name is provided in
the GIC options, use anonymous PKINIT to acquire an armor ticket into
a temporary memory ccache.  Refactor krb5int_fast_as_armor() and add
an armor_ccache parameter so that we can pass in the memory ccache
without modifying the caller's GIC options.

[ghudson@mit.edu: edited commit messages, documentation, and comments;
refactored some code changes for simplicity]

ticket: 9226 (new)

doc/admin/conf_files/krb5_conf.rst
src/include/k5-int.h
src/include/k5-trace.h
src/lib/krb5/krb/fast.c
src/lib/krb5/krb/fast.h
src/lib/krb5/krb/get_in_tkt.c
src/lib/krb5/krb/init_creds_ctx.h
src/tests/gssapi/t_gssapi.py
src/tests/t_pkinit.py

index e0c7a633094e4f2d35d6ab9d2f501fc0c9cbad3f..d7ea5334430552fc209914899b7e7b41a01f686d 100644 (file)
@@ -487,6 +487,17 @@ following tags may be specified in the realm's subsection:
     names to local user names.  The tag is the mapping name, and the
     value is the corresponding local user name.
 
     names to local user names.  The tag is the mapping name, and the
     value is the corresponding local user name.
 
+**auto_fast_armor**
+    If this flag is true, initial credential acquisition will acquire
+    an anonymous PKINIT ticket to use as FAST armor before making the
+    real ticket request, unless an armor ccache is already configured
+    (as with kinit's **-T** option).  This flag allows
+    preauthentication mechanisms that require FAST, such as OTP, to
+    work without a pre-existing ticket.  This flag should only be set
+    for realms known to support anonymous PKINIT.  This flag has no
+    effect if the client principal is itself the anonymous principal.
+    The default value is false.  New in release 1.23.
+
 **default_domain**
     This tag specifies the domain used to expand hostnames when
     translating Kerberos 4 service principals to Kerberos 5 principals
 **default_domain**
     This tag specifies the domain used to expand hostnames when
     translating Kerberos 4 service principals to Kerberos 5 principals
index 20611d06728d5f63a11feec8fe99a4ec50f6dfb7..8327c22624ac6a7915428515f6a4d80ff2ac4b1e 100644 (file)
@@ -185,6 +185,7 @@ typedef unsigned char   u_char;
 #define KRB5_CONF_ALLOW_WEAK_CRYPTO            "allow_weak_crypto"
 #define KRB5_CONF_AUTH_TO_LOCAL                "auth_to_local"
 #define KRB5_CONF_AUTH_TO_LOCAL_NAMES          "auth_to_local_names"
 #define KRB5_CONF_ALLOW_WEAK_CRYPTO            "allow_weak_crypto"
 #define KRB5_CONF_AUTH_TO_LOCAL                "auth_to_local"
 #define KRB5_CONF_AUTH_TO_LOCAL_NAMES          "auth_to_local_names"
+#define KRB5_CONF_AUTO_FAST_ARMOR              "auto_fast_armor"
 #define KRB5_CONF_CANONICALIZE                 "canonicalize"
 #define KRB5_CONF_CCACHE_TYPE                  "ccache_type"
 #define KRB5_CONF_CLOCKSKEW                    "clockskew"
 #define KRB5_CONF_CANONICALIZE                 "canonicalize"
 #define KRB5_CONF_CCACHE_TYPE                  "ccache_type"
 #define KRB5_CONF_CLOCKSKEW                    "clockskew"
index 5d0be63bf053e452244b1acb8ea01dbcf0304cdb..5aefd098a65fa8bed8755d3d542344985af01315 100644 (file)
@@ -173,8 +173,8 @@ void krb5int_trace(krb5_context context, const char *fmt, ...);
 #define TRACE_DNS_URI_SEND(c, domain)                   \
     TRACE(c, "Sending DNS URI query for {str}", domain)
 
 #define TRACE_DNS_URI_SEND(c, domain)                   \
     TRACE(c, "Sending DNS URI query for {str}", domain)
 
-#define TRACE_FAST_ARMOR_CCACHE(c, ccache_name)         \
-    TRACE(c, "FAST armor ccache: {str}", ccache_name)
+#define TRACE_FAST_ARMOR_CCACHE(c, ccache)              \
+    TRACE(c, "FAST armor ccache: {ccache}", ccache)
 #define TRACE_FAST_ARMOR_CCACHE_KEY(c, keyblock)                \
     TRACE(c, "Armor ccache session key: {keyblock}", keyblock)
 #define TRACE_FAST_ARMOR_KEY(c, keyblock)               \
 #define TRACE_FAST_ARMOR_CCACHE_KEY(c, keyblock)                \
     TRACE(c, "Armor ccache session key: {keyblock}", keyblock)
 #define TRACE_FAST_ARMOR_KEY(c, keyblock)               \
@@ -217,6 +217,8 @@ void krb5int_trace(krb5_context context, const char *fmt, ...);
 
 #define TRACE_INIT_CREDS(c, princ)                              \
     TRACE(c, "Getting initial credentials for {princ}", princ)
 
 #define TRACE_INIT_CREDS(c, princ)                              \
     TRACE(c, "Getting initial credentials for {princ}", princ)
+#define TRACE_INIT_CREDS_AUTO_FAST_ARMOR(c)                             \
+    TRACE(c, "Acquiring anonymous PKINIT armor ticket for FAST")
 #define TRACE_INIT_CREDS_AS_KEY_GAK(c, keyblock)                        \
     TRACE(c, "AS key obtained from gak_fct: {keyblock}", keyblock)
 #define TRACE_INIT_CREDS_AS_KEY_PREAUTH(c, keyblock)                    \
 #define TRACE_INIT_CREDS_AS_KEY_GAK(c, keyblock)                        \
     TRACE(c, "AS key obtained from gak_fct: {keyblock}", keyblock)
 #define TRACE_INIT_CREDS_AS_KEY_PREAUTH(c, keyblock)                    \
index 62c9f0841f6aba43fbf314406ad5bed316c80327..f9bef7f6b0553124773e818e63e340045f2e3e8c 100644 (file)
@@ -171,55 +171,62 @@ krb5int_fast_prep_req_body(krb5_context context,
 krb5_error_code
 krb5int_fast_as_armor(krb5_context context,
                       struct krb5int_fast_request_state *state,
 krb5_error_code
 krb5int_fast_as_armor(krb5_context context,
                       struct krb5int_fast_request_state *state,
-                      krb5_get_init_creds_opt *opt, krb5_kdc_req *request)
+                      krb5_get_init_creds_opt *opt,
+                      krb5_ccache armor_ccache, krb5_kdc_req *request)
 {
     krb5_error_code retval = 0;
     krb5_ccache ccache = NULL;
     krb5_principal target_principal = NULL;
 {
     krb5_error_code retval = 0;
     krb5_ccache ccache = NULL;
     krb5_principal target_principal = NULL;
-    krb5_data *target_realm;
+    krb5_data *target_realm, config_data = empty_data();
     const char *ccname = k5_gic_opt_get_fast_ccache_name(opt);
     krb5_flags fast_flags;
 
     krb5_clear_error_message(context);
     target_realm = &request->server->realm;
     const char *ccname = k5_gic_opt_get_fast_ccache_name(opt);
     krb5_flags fast_flags;
 
     krb5_clear_error_message(context);
     target_realm = &request->server->realm;
-    if (ccname != NULL) {
-        TRACE_FAST_ARMOR_CCACHE(context, ccname);
-        state->fast_state_flags |= KRB5INT_FAST_ARMOR_AVAIL;
+
+    if (armor_ccache == NULL) {
+        /* Stop if no armor ccache was provided by the direct caller or the GIC
+         * options. */
+        if (ccname == NULL)
+            return 0;
+        /* Resolve the armor ccache name provided in the GIC options. */
         retval = krb5_cc_resolve(context, ccname, &ccache);
         retval = krb5_cc_resolve(context, ccname, &ccache);
-        if (retval == 0) {
-            retval = krb5int_tgtname(context, target_realm, target_realm,
-                                     &target_principal);
-        }
-        if (retval == 0) {
-            krb5_data config_data;
-            config_data.data = NULL;
-            retval = krb5_cc_get_config(context, ccache, target_principal,
-                                        KRB5_CC_CONF_FAST_AVAIL, &config_data);
-            if ((retval == 0) && config_data.data) {
-                TRACE_FAST_CCACHE_CONFIG(context);
-                state->fast_state_flags |= KRB5INT_FAST_DO_FAST;
-            }
-            krb5_free_data_contents(context, &config_data);
-            retval = 0;
-        }
-        fast_flags = k5_gic_opt_get_fast_flags(opt);
-        if (fast_flags & KRB5_FAST_REQUIRED) {
-            TRACE_FAST_REQUIRED(context);
-            state->fast_state_flags |= KRB5INT_FAST_DO_FAST;
-        }
-        if (retval == 0 && (state->fast_state_flags & KRB5INT_FAST_DO_FAST)) {
-            retval = fast_armor_ap_request(context, state, ccache,
-                                           target_principal);
-        }
-        if (retval != 0) {
-            k5_prependmsg(context, retval,
-                          _("Error constructing AP-REQ armor"));
-        }
+        if (retval)
+            goto cleanup;
+        armor_ccache = ccache;
+    }
+
+    TRACE_FAST_ARMOR_CCACHE(context, armor_ccache);
+
+    state->fast_state_flags |= KRB5INT_FAST_ARMOR_AVAIL;
+    retval = krb5int_tgtname(context, target_realm, target_realm,
+                             &target_principal);
+    if (retval)
+        goto cleanup;
+
+    retval = krb5_cc_get_config(context, armor_ccache, target_principal,
+                                KRB5_CC_CONF_FAST_AVAIL, &config_data);
+    if (!retval && config_data.data != NULL) {
+        TRACE_FAST_CCACHE_CONFIG(context);
+        state->fast_state_flags |= KRB5INT_FAST_DO_FAST;
     }
     }
+
+    fast_flags = k5_gic_opt_get_fast_flags(opt);
+    if (fast_flags & KRB5_FAST_REQUIRED) {
+        TRACE_FAST_REQUIRED(context);
+        state->fast_state_flags |= KRB5INT_FAST_DO_FAST;
+    }
+
+    retval = fast_armor_ap_request(context, state, armor_ccache,
+                                   target_principal);
+    if (retval)
+        k5_prependmsg(context, retval, _("Error constructing AP-REQ armor"));
+
+cleanup:
     if (ccache)
         krb5_cc_close(context, ccache);
     if (ccache)
         krb5_cc_close(context, ccache);
-    if (target_principal)
-        krb5_free_principal(context, target_principal);
+    krb5_free_principal(context, target_principal);
+    krb5_free_data_contents(context, &config_data);
     return retval;
 }
 
     return retval;
 }
 
index 7156ea203f1122051042fac0175fc20fac9c2579..1dbce2f3f014decda7b63a2ca1835514e052facc 100644 (file)
@@ -83,7 +83,8 @@ krb5int_fast_free_state(krb5_context context,
 krb5_error_code
 krb5int_fast_as_armor(krb5_context context,
                       struct krb5int_fast_request_state *state,
 krb5_error_code
 krb5int_fast_as_armor(krb5_context context,
                       struct krb5int_fast_request_state *state,
-                      krb5_get_init_creds_opt *opt, krb5_kdc_req *request);
+                      krb5_get_init_creds_opt *opt,
+                      krb5_ccache auto_armor_ccache, krb5_kdc_req *request);
 
 krb5_error_code
 krb5int_fast_reply_key(krb5_context context,
 
 krb5_error_code
 krb5int_fast_reply_key(krb5_context context,
index 00a5cceea5ac37f3e6ed060dc537ca9e537af87c..63ec57e58300601598708481252429a5fc87f24a 100644 (file)
@@ -537,6 +537,10 @@ krb5_init_creds_free(krb5_context context,
     krb5_free_data_contents(context, &ctx->salt);
     krb5_free_data_contents(context, &ctx->s2kparams);
     krb5_free_keyblock_contents(context, &ctx->as_key);
     krb5_free_data_contents(context, &ctx->salt);
     krb5_free_data_contents(context, &ctx->s2kparams);
     krb5_free_keyblock_contents(context, &ctx->as_key);
+    krb5_init_creds_free(context, ctx->auto_armor_ctx);
+    krb5_get_init_creds_opt_free(context, ctx->auto_armor_opt);
+    if (ctx->auto_armor_ccache != NULL)
+        krb5_cc_destroy(context, ctx->auto_armor_ccache);
     k5_json_release(ctx->cc_config_in);
     k5_json_release(ctx->cc_config_out);
     free(ctx);
     k5_json_release(ctx->cc_config_in);
     k5_json_release(ctx->cc_config_out);
     free(ctx);
@@ -771,6 +775,87 @@ encts_disabled(profile_t profile, const krb5_data *realm)
     return (ret == 0) ? bval : FALSE;
 }
 
     return (ret == 0) ? bval : FALSE;
 }
 
+/* Return true if auto_fast_armor is enabled for realm. */
+static krb5_boolean
+auto_fast_armor_enabled(profile_t profile, const krb5_data *realm)
+{
+    krb5_error_code ret;
+    char *realmstr;
+    int bval;
+
+    realmstr = k5memdup0(realm->data, realm->length, &ret);
+    if (realmstr == NULL)
+        return FALSE;
+    ret = profile_get_boolean(profile, KRB5_CONF_REALMS, realmstr,
+                              KRB5_CONF_AUTO_FAST_ARMOR, FALSE, &bval);
+    free(realmstr);
+    return (ret == 0) ? bval : FALSE;
+}
+
+/*
+ * Return true if ctx should first acquire FAST armor using anonymous PKINIT.
+ * This decision is primarily dependent on the auto_fast_armor config option,
+ * but we don't acquire armor if the caller passed in an armor ccache or if the
+ * state machine is already performing an anonymous PKINIT request.
+ */
+static krb5_boolean
+want_auto_armor(krb5_context context, krb5_init_creds_context ctx)
+{
+    if (k5_gic_opt_get_fast_ccache_name(ctx->opt) != NULL)
+        return FALSE;
+    if (krb5_principal_compare_any_realm(context, ctx->request->client,
+                                         krb5_anonymous_principal()))
+        return FALSE;
+    return auto_fast_armor_enabled(context->profile,
+                                   &ctx->request->client->realm);
+}
+
+/* Create a memory ccache and nested init_creds context for acquiring FAST amor
+ * via anonymous PKINIT. */
+static krb5_error_code
+begin_auto_armor(krb5_context context, krb5_init_creds_context ctx)
+{
+    krb5_error_code ret;
+    krb5_principal anon_princ = NULL;
+    const krb5_data *realm = &ctx->request->client->realm;
+
+    TRACE_INIT_CREDS_AUTO_FAST_ARMOR(context);
+
+    ret = krb5_cc_new_unique(context, "MEMORY", NULL, &ctx->auto_armor_ccache);
+    if (ret)
+        goto cleanup;
+
+    ret = krb5_build_principal_ext(context, &anon_princ,
+                                   realm->length, realm->data,
+                                   strlen(KRB5_WELLKNOWN_NAMESTR),
+                                   KRB5_WELLKNOWN_NAMESTR,
+                                   strlen(KRB5_ANONYMOUS_PRINCSTR),
+                                   KRB5_ANONYMOUS_PRINCSTR, 0);
+    if (ret)
+        goto cleanup;
+    anon_princ->type = KRB5_NT_WELLKNOWN;
+
+    ret = krb5_get_init_creds_opt_alloc(context, &ctx->auto_armor_opt);
+    if (ret)
+        goto cleanup;
+    krb5_get_init_creds_opt_set_anonymous(ctx->auto_armor_opt, 1);
+    krb5_get_init_creds_opt_set_tkt_life(ctx->auto_armor_opt, 60 * 60);
+    ret = krb5_get_init_creds_opt_set_out_ccache(context, ctx->auto_armor_opt,
+                                                 ctx->auto_armor_ccache);
+    if (ret)
+        goto cleanup;
+
+    ret = krb5_init_creds_init(context, anon_princ, NULL, NULL,
+                               ctx->start_time, ctx->auto_armor_opt,
+                               &ctx->auto_armor_ctx);
+    if (ret)
+        goto cleanup;
+
+cleanup:
+    krb5_free_principal(context, anon_princ);
+    return ret;
+}
+
 /**
  * Throw away any pre-authentication realm state and begin with a
  * unauthenticated or optimistically authenticated request.  If fast_upgrade is
 /**
  * Throw away any pre-authentication realm state and begin with a
  * unauthenticated or optimistically authenticated request.  If fast_upgrade is
@@ -827,7 +912,7 @@ restart_init_creds_loop(krb5_context context, krb5_init_creds_context ctx,
         goto cleanup;
 
     code = krb5int_fast_as_armor(context, ctx->fast_state, ctx->opt,
         goto cleanup;
 
     code = krb5int_fast_as_armor(context, ctx->fast_state, ctx->opt,
-                                 ctx->request);
+                                 ctx->auto_armor_ccache, ctx->request);
     if (code != 0)
         goto cleanup;
     /* give the preauth plugins a chance to prep the request body */
     if (code != 0)
         goto cleanup;
     /* give the preauth plugins a chance to prep the request body */
@@ -1043,6 +1128,12 @@ krb5_init_creds_init(krb5_context context,
         ctx->request->client->type = KRB5_NT_WELLKNOWN;
     }
 
         ctx->request->client->type = KRB5_NT_WELLKNOWN;
     }
 
+    if (want_auto_armor(context, ctx)) {
+        code = begin_auto_armor(context, ctx);
+        if (code)
+            goto cleanup;
+    }
+
     *pctx = ctx;
     ctx = NULL;
 
     *pctx = ctx;
     ctx = NULL;
 
@@ -1891,7 +1982,23 @@ krb5_init_creds_step(krb5_context context,
     if (code)
         return code;
 
     if (code)
         return code;
 
-    if (in->length != 0) {
+    if (ctx->auto_armor_ctx != NULL) {
+        /* Drive the nested context to acquire an anonymous TGT. */
+        code = krb5_init_creds_step(context, ctx->auto_armor_ctx, in, out,
+                                    realm, flags);
+        if (code || (*flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE))
+            return code;
+
+        /* The nested context is complete.  Discard it to signal that the outer
+         * state machine should proceed using auto_armor_ccache. */
+        krb5_init_creds_free(context, ctx->auto_armor_ctx);
+        ctx->auto_armor_ctx = NULL;
+
+        /* Begin the actual AS request, asserting that FAST is available. */
+        code = restart_init_creds_loop(context, ctx, TRUE);
+        if (code)
+            return code;
+    } else if (in->length != 0) {
         code = init_creds_step_reply(context, ctx, in);
         if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
             code2 = krb5int_copy_data_contents(context,
         code = init_creds_step_reply(context, ctx, in);
         if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
             code2 = krb5int_copy_data_contents(context,
index 710b77810b28a910eaffe64b8138f72bfa979d9e..caaff61e88708b615e6b817632e151b9eecb0836 100644 (file)
@@ -64,6 +64,10 @@ struct _krb5_init_creds_context {
     krb5_boolean info_pa_permitted;
     krb5_boolean restarted;
     krb5_boolean encts_disabled;
     krb5_boolean info_pa_permitted;
     krb5_boolean restarted;
     krb5_boolean encts_disabled;
+    /* Automatic FAST armor state machine and memory cache */
+    krb5_init_creds_context auto_armor_ctx;
+    krb5_get_init_creds_opt *auto_armor_opt;
+    krb5_ccache auto_armor_ccache;
     struct krb5_responder_context_st rctx;
     krb5_preauthtype current_preauth_type;
     krb5_preauthtype selected_preauth_type;
     struct krb5_responder_context_st rctx;
     krb5_preauthtype current_preauth_type;
     krb5_preauthtype selected_preauth_type;
index 149f46d5c9145f6752e92d468047b181a34f6fcf..18a12d56bb0308daf38263f8d7cb088a8f72d6b8 100755 (executable)
@@ -255,4 +255,22 @@ check_lifetime('actx gss_accept_sec_context', ln[7], 8000 * 86400 + 300)
 check_lifetime('actx gss_inquire_context', ln[8], 8000 * 86400 + 300)
 check_lifetime('actx gss_context_time', ln[9], 8000 * 86400 + 300)
 
 check_lifetime('actx gss_inquire_context', ln[8], 8000 * 86400 + 300)
 check_lifetime('actx gss_context_time', ln[9], 8000 * 86400 + 300)
 
+realm.stop()
+
+# Test auto_fast_armor with IAKERB driving the state machine.
+if pkinit_enabled:
+    mark('IAKERB with auto_fast_armor')
+    afa_conf = {'realms': {'$realm': {'auto_fast_armor': 'true'}}}
+    realm = K5Realm(krb5_conf=afa_conf, get_creds=False, pkinit=True)
+    realm.run([kadminl, 'modprinc', '+preauth', realm.user_princ])
+    realm.addprinc('WELLKNOWN/ANONYMOUS')
+    msgs = ('Acquiring anonymous PKINIT armor ticket for FAST',
+            'Getting initial credentials for WELLKNOWN/ANONYMOUS',
+            'Using FAST due to armor ccache negotiation result',
+            'Preauth module encrypted_challenge (138) (real) returned: 0')
+    realm.run(['./t_iakerb', 'p:' + realm.user_princ, password('user'),
+               'h:host@' + hostname, 'h:host'], expected_trace=msgs)
+else:
+    print('Skipping IAKERB auto_fast_armor test: PKINIT not built')
+
 success('GSSAPI tests')
 success('GSSAPI tests')
index 91d4630a0a3bff61588bb1158c30ed4f2afa6219..0b6c0382ca028ff09aae196c9bdc7406e7235205 100755 (executable)
@@ -105,10 +105,6 @@ realm.kinit(realm.user_princ, password=password('user'))
 realm.klist(realm.user_princ)
 realm.run([kvno, realm.host_princ])
 
 realm.klist(realm.user_princ)
 realm.run([kvno, realm.host_princ])
 
-# Having tested password preauth, remove the keys for better error
-# reporting.
-realm.run([kadminl, 'purgekeys', '-all', realm.user_princ])
-
 # Test anonymous PKINIT.
 mark('anonymous')
 realm.kinit('@%s' % realm.realm, flags=['-n'], expected_code=1,
 # Test anonymous PKINIT.
 mark('anonymous')
 realm.kinit('@%s' % realm.realm, flags=['-n'], expected_code=1,
@@ -124,6 +120,29 @@ if '97:' in out:
 realm.run([klist, '-C'], expected_msg='start_realm = KRBTEST.COM')
 realm.run([kvno, '-S', 'host', hostname])
 
 realm.run([klist, '-C'], expected_msg='start_realm = KRBTEST.COM')
 realm.run([kvno, '-S', 'host', hostname])
 
+# Test auto_fast_armor.
+mark('auto_fast_armor')
+afa_conf = {'realms': {'$realm': {'auto_fast_armor': 'true'}}}
+afa_env = realm.special_env('auto_fast', False, krb5_conf=afa_conf)
+msgs = ('Acquiring anonymous PKINIT armor ticket for FAST',
+        'Getting initial credentials for WELLKNOWN/ANONYMOUS@%s' % realm.realm,
+        'Using FAST due to armor ccache negotiation result',
+        'Preauth module encrypted_challenge (138) (real) returned: 0/Success')
+realm.kinit(realm.user_princ, password=password('user'), env=afa_env,
+            expected_trace=msgs)
+realm.klist(realm.user_princ)
+
+# auto_fast_armor shouldn't trigger for direct use of anonymous PKINIT.
+mark('anonymous (auto_fast_armor=true)')
+out, trace = realm.kinit('@%s' % realm.realm, flags=['-n'], env=afa_env,
+                         return_trace=True)
+if 'Acquiring anonymous PKINIT armor ticket for FAST' in trace:
+    fail('auto_fast_armor improperly triggered for anonymous kinit')
+
+# For the remaining tests in this realm, remove the keys on user for
+# better error reporting (by preventing encrypted timestamp fallback).
+realm.run([kadminl, 'purgekeys', '-all', realm.user_princ])
+
 # Test anonymous kadmin.
 mark('anonymous kadmin')
 f = open(os.path.join(realm.testdir, 'acl'), 'a')
 # Test anonymous kadmin.
 mark('anonymous kadmin')
 f = open(os.path.join(realm.testdir, 'acl'), 'a')