Implement restrict_anonymous_to_tgt realm flag
authorGreg Hudson <ghudson@mit.edu>
Wed, 1 Dec 2010 20:01:46 +0000 (20:01 +0000)
committerGreg Hudson <ghudson@mit.edu>
Wed, 1 Dec 2010 20:01:46 +0000 (20:01 +0000)
Implement a new realm flag to reject ticket requests from anonymous
principals to any principal other than the local TGT.  Allows FAST to
be deployed using anonymous tickets as armor in realms where the set
of authenticatable users must be constrained.

ticket: 6829
target_version: 1.9
tags: pullup

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@24547 dc483132-0cff-0310-8789-dd5450dbe970

doc/admin.texinfo
src/include/adm.h
src/include/k5-int.h
src/kdc/extern.h
src/kdc/kdc_util.c
src/kdc/main.c
src/lib/kadm5/admin.h
src/lib/kadm5/alt_prof.c
src/tests/t_anonpkinit.py
src/util/k5test.py

index 5dcc0d3742a859f404054f4ff0e958a4e99874b5..b6cc8e645853a7af23adcd7df0c0110f841ba2a5 100644 (file)
@@ -1562,7 +1562,11 @@ If you wish to change this (which we do not recommend, because the
 current implementation has little protection against denial-of-service
 attacks), the standard port number assigned for Kerberos TCP traffic
 is port 88.
--@end table
+
+@itemx restrict_anonymous_to_kdc
+This flag determines the default value of restrict_anonymous_to_kdc for
+realms.  The default value is @code{false}.
+@end table
 
 @node realms (kdc.conf), pkinit kdc options, kdcdefaults, kdc.conf
 @subsection [realms]
@@ -1742,6 +1746,15 @@ software on its application servers updated but another has not.
 
 This option defaults to @code{true}.
 
+@itemx restrict_anonymous_to_tgt
+A boolean value (@code{true}, @code{false}).  If set to @code{true}, the
+KDC will reject ticket requests from anonymous principals to service
+principals other than the realm's ticket-granting service.  This option
+allows anonymous PKINIT to be enabled for use as FAST armor tickets
+without allowing anonymous authentication to services.  By default, the
+value of restrict_anonymous_to_tgt as specified in the [kdcdefaults]
+section is used.
+
 @end table
 
 @node pkinit kdc options, Sample kdc.conf File, realms (kdc.conf), kdc.conf
index 68f3c7a897ff107b0dad74fafaee48fc46875ffd..c23cd89e3444418f55199ec2772ba5e216c08af2 100644 (file)
@@ -210,6 +210,7 @@ typedef struct __krb5_realm_params {
     krb5_flags          realm_flags;
     krb5_key_salt_tuple *realm_keysalts;
     unsigned int        realm_reject_bad_transit:1;
+    unsigned int        realm_restrict_anon:1;
     unsigned int        realm_kadmind_port_valid:1;
     unsigned int        realm_enctype_valid:1;
     unsigned int        realm_max_life_valid:1;
@@ -217,6 +218,7 @@ typedef struct __krb5_realm_params {
     unsigned int        realm_expiration_valid:1;
     unsigned int        realm_flags_valid:1;
     unsigned int        realm_reject_bad_transit_valid:1;
+    unsigned int        realm_restrict_anon_valid:1;
     krb5_int32          realm_num_keysalts;
 } krb5_realm_params;
 #endif  /* KRB5_ADM_H__ */
index 9a81d3bbccc17c7a70494f683c393729facd1533..ac46b460068fd6ded71319cac17007cf2adc8c37 100644 (file)
@@ -263,6 +263,7 @@ typedef INT64_TYPE krb5_int64;
 #define KRB5_CONF_REALM_TRY_DOMAINS           "realm_try_domains"
 #define KRB5_CONF_REJECT_BAD_TRANSIT          "reject_bad_transit"
 #define KRB5_CONF_RENEW_LIFETIME              "renew_lifetime"
+#define KRB5_CONF_RESTRICT_ANONYMOUS_TO_TGT   "restrict_anonymous_to_tgt"
 #define KRB5_CONF_SAFE_CHECKSUM_TYPE          "safe_checksum_type"
 #define KRB5_CONF_SUPPORTED_ENCTYPES          "supported_enctypes"
 #define KRB5_CONF_TICKET_LIFETIME             "ticket_lifetime"
index 24bad20a5083b8865d14d4239778f672682d58a8..236e5ccd9c6110fa942659f23361c3e47261091e 100644 (file)
@@ -74,6 +74,7 @@ typedef struct __kdc_realm_data {
     krb5_deltat         realm_maxlife;  /* Maximum ticket life for realm    */
     krb5_deltat         realm_maxrlife; /* Maximum renewable life for realm */
     krb5_boolean        realm_reject_bad_transit; /* Accept unverifiable transited_realm ? */
+    krb5_boolean        realm_restrict_anon;  /* Anon to local TGT only */
 } kdc_realm_t;
 
 extern kdc_realm_t      **kdc_realmlist;
@@ -95,6 +96,7 @@ kdc_realm_t *find_realm_data (char *, krb5_ui_4);
 #define master_princ                    kdc_active_realm->realm_mprinc
 #define tgs_server                      kdc_active_realm->realm_tgsprinc
 #define reject_bad_transit              kdc_active_realm->realm_reject_bad_transit
+#define restrict_anon                   kdc_active_realm->realm_restrict_anon
 
 /* various externs for KDC */
 extern krb5_data        empty_string;   /* an empty string */
index 691ece06c90980a33789cc6a7f0c37953581d530..62fea6fdb69043236cf42ba8e8ea6a45c4563053 100644 (file)
@@ -838,6 +838,21 @@ errcode_to_protocol(krb5_error_code code)
     return (protcode >= 0 && protcode <= 128) ? protcode : KRB_ERR_GENERIC;
 }
 
+/* Return -1 if the AS or TGS request is disallowed due to KDC policy on
+ * anonymous tickets. */
+static int
+check_anon(krb5_context context, krb5_principal client, krb5_principal server)
+{
+    /* If restrict_anon is set, reject requests from anonymous to principals
+     * other than the local TGT. */
+    if (restrict_anon &&
+        krb5_principal_compare_any_realm(context, client,
+                                         krb5_anonymous_principal()) &&
+        !krb5_principal_compare(context, server, tgs_server))
+        return -1;
+    return 0;
+}
+
 /*
  * Routines that validate a AS request; checks a lot of things.  :-)
  *
@@ -957,6 +972,11 @@ validate_as_request(register krb5_kdc_req *request, krb5_db_entry client,
         return(KDC_ERR_MUST_USE_USER2USER);
     }
 
+    if (check_anon(kdc_context, request->client, request->server) != 0) {
+        *status = "ANONYMOUS NOT ALLOWED";
+        return(KDC_ERR_POLICY);
+    }
+
     /* Perform KDB module policy checks. */
     ret = krb5_db_check_policy_as(kdc_context, request, &client, &server,
                                   kdc_time, status, e_data);
@@ -1379,6 +1399,12 @@ validate_tgs_request(register krb5_kdc_req *request, krb5_db_entry server,
         return KRB_ERR_GENERIC;
     }
 
+    if (check_anon(kdc_context, ticket->enc_part2->client,
+                   request->server) != 0) {
+        *status = "ANONYMOUS NOT ALLOWED";
+        return(KDC_ERR_POLICY);
+    }
+
     /* Perform KDB module policy checks. */
     ret = krb5_db_check_policy_tgs(kdc_context, request, &server,
                                    ticket, status, e_data);
index 5b90bd592699afcfe1f87428144f89d155a053d8..57cd226fb08e18f84326b9a7bfd5d101c10dc432 100644 (file)
@@ -279,8 +279,8 @@ handle_referral_params(krb5_realm_params *rparams,
 static krb5_error_code
 init_realm(kdc_realm_t *rdp, char *realm, char *def_mpname,
            krb5_enctype def_enctype, char *def_udp_ports, char *def_tcp_ports,
-           krb5_boolean def_manual, char **db_args, char *no_refrls,
-           char *host_based_srvcs)
+           krb5_boolean def_manual, krb5_boolean def_restrict_anon,
+           char **db_args, char *no_refrls, char *host_based_srvcs)
 {
     krb5_error_code     kret;
     krb5_boolean        manual;
@@ -356,6 +356,11 @@ init_realm(kdc_realm_t *rdp, char *realm, char *def_mpname,
     } else
         manual = def_manual;
 
+    if (rparams && rparams->realm_restrict_anon_valid)
+        rdp->realm_restrict_anon = rparams->realm_restrict_anon;
+    else
+        rdp->realm_restrict_anon = def_restrict_anon;
+
     /* Handle master key type */
     if (rparams && rparams->realm_enctype_valid)
         rdp->realm_mkey.enctype = (krb5_enctype) rparams->realm_enctype;
@@ -655,6 +660,7 @@ initialize_realms(krb5_context kcontext, int argc, char **argv)
     krb5_enctype        menctype = ENCTYPE_UNKNOWN;
     kdc_realm_t         *rdatap = NULL;
     krb5_boolean        manual = FALSE;
+    krb5_boolean        def_restrict_anon;
     char                *default_udp_ports = 0;
     char                *default_tcp_ports = 0;
     krb5_pointer        aprof;
@@ -678,6 +684,9 @@ initialize_realms(krb5_context kcontext, int argc, char **argv)
         hierarchy[1] = KRB5_CONF_MAX_DGRAM_REPLY_SIZE;
         if (krb5_aprof_get_int32(aprof, hierarchy, TRUE, &max_dgram_reply_size))
             max_dgram_reply_size = MAX_DGRAM_SIZE;
+        hierarchy[1] = KRB5_CONF_RESTRICT_ANONYMOUS_TO_TGT;
+        if (krb5_aprof_get_boolean(aprof, hierarchy, TRUE, &def_restrict_anon))
+            def_restrict_anon = FALSE;
         hierarchy[1] = KRB5_CONF_NO_HOST_REFERRAL;
         if (krb5_aprof_get_string_all(aprof, hierarchy, &no_refrls))
             no_refrls = 0;
@@ -736,7 +745,8 @@ initialize_realms(krb5_context kcontext, int argc, char **argv)
                 if ((rdatap = (kdc_realm_t *) malloc(sizeof(kdc_realm_t)))) {
                     if ((retval = init_realm(rdatap, optarg, mkey_name,
                                              menctype, default_udp_ports,
-                                             default_tcp_ports, manual, db_args,
+                                             default_tcp_ports, manual,
+                                             def_restrict_anon, db_args,
                                              no_refrls, host_based_srvcs))) {
                         fprintf(stderr,
                                 "%s: cannot initialize realm %s - see log file for details\n",
@@ -848,8 +858,8 @@ initialize_realms(krb5_context kcontext, int argc, char **argv)
         if ((rdatap = (kdc_realm_t *) malloc(sizeof(kdc_realm_t)))) {
             if ((retval = init_realm(rdatap, lrealm, mkey_name, menctype,
                                      default_udp_ports, default_tcp_ports,
-                                     manual, db_args, no_refrls,
-                                     host_based_srvcs))) {
+                                     manual, def_restrict_anon, db_args,
+                                     no_refrls, host_based_srvcs))) {
                 fprintf(stderr,"%s: cannot initialize realm %s - see log file for details\n",
                         argv[0], lrealm);
                 exit(1);
index 99837033bee397dc403c9de80b1d62be88b072c1..8b59c6b5bbcff5c98637fdde573f38b6bca70a2e 100644 (file)
@@ -291,6 +291,7 @@ typedef struct __krb5_realm_params {
     krb5_flags          realm_flags;
     krb5_key_salt_tuple *realm_keysalts;
     unsigned int        realm_reject_bad_transit:1;
+    unsigned int        realm_restrict_anon:1;
     unsigned int        realm_kadmind_port_valid:1;
     unsigned int        realm_enctype_valid:1;
     unsigned int        realm_max_life_valid:1;
@@ -298,6 +299,7 @@ typedef struct __krb5_realm_params {
     unsigned int        realm_expiration_valid:1;
     unsigned int        realm_flags_valid:1;
     unsigned int        realm_reject_bad_transit_valid:1;
+    unsigned int        realm_restrict_anon_valid:1;
     krb5_int32          realm_num_keysalts;
 } krb5_realm_params;
 
index 6a79655126fbbe3fd72bc02655f1dca9fb1a0997..cdd732113adee3e52d5a5a669056888522d859cd 100644 (file)
@@ -1058,6 +1058,12 @@ krb5_read_realm_params(kcontext, realm, rparamp)
         rparams->realm_reject_bad_transit_valid = 1;
     }
 
+    hierarchy[2] = KRB5_CONF_RESTRICT_ANONYMOUS_TO_TGT;
+    if (!krb5_aprof_get_boolean(aprofile, hierarchy, TRUE, &bvalue)) {
+        rparams->realm_restrict_anon = bvalue;
+        rparams->realm_restrict_anon_valid = 1;
+    }
+
     hierarchy[2] = KRB5_CONF_NO_HOST_REFERRAL;
     if (!krb5_aprof_get_string_all(aprofile, hierarchy, &no_refrls))
         rparams->realm_no_host_referral = no_refrls;
index 0149a671b1924bb2175d01554b6d6fe6eff7c993..ddb96919b423185dc6311a1839571188d17bf45a 100644 (file)
@@ -25,10 +25,23 @@ pkinit_krb5_conf = {
     }
 }
 
-realm = K5Realm(krb5_conf=pkinit_krb5_conf, create_user=False,
-                create_host=False)
+restrictive_kdc_conf = {
+    'all': { 'realms' : { '$realm' : {
+                'restrict_anonymous_to_tgt' : 'true' } } } }
+
+# In the basic test, anonymous is not restricted, so kvno should succeed.
+realm = K5Realm(krb5_conf=pkinit_krb5_conf, create_user=False)
 realm.addprinc('WELLKNOWN/ANONYMOUS')
 realm.kinit('@%s' % realm.realm, flags=['-n'])
 realm.klist('WELLKNOWN/ANONYMOUS@WELLKNOWN:ANONYMOUS')
+realm.run_as_client([kvno, realm.host_princ])
+realm.stop()
+
+# Now try again with anonymous restricted; kvno should fail.
+realm = K5Realm(krb5_conf=pkinit_krb5_conf, kdc_conf=restrictive_kdc_conf,
+                create_user=False)
+realm.addprinc('WELLKNOWN/ANONYMOUS')
+realm.kinit('@%s' % realm.realm, flags=['-n'])
+realm.run_as_client([kvno, realm.host_princ], expected_code=1)
 
 success('Anonymous PKINIT.')
index a710eee3aeee812c2233f0e798ca8f30547122a3..a032d78b5baa59f52fa05c7dbc2e4248f0da967f 100644 (file)
@@ -181,6 +181,7 @@ Scripts may use the following functions and variables:
   - ktutil
   - kinit
   - klist
+  - kvno
   - kdestroy
   - kpasswd
   - t_inetd
@@ -1054,6 +1055,7 @@ kdb5_util = os.path.join(buildtop, 'kadmin', 'dbutil', 'kdb5_util')
 ktutil = os.path.join(buildtop, 'kadmin', 'ktutil', 'ktutil')
 kinit = os.path.join(buildtop, 'clients', 'kinit', 'kinit')
 klist = os.path.join(buildtop, 'clients', 'klist', 'klist')
+kvno = os.path.join(buildtop, 'clients', 'kvno', 'kvno')
 kdestroy = os.path.join(buildtop, 'clients', 'kdestroy', 'kdestroy')
 kpasswd = os.path.join(buildtop, 'clients', 'kpasswd', 'kpasswd')
 t_inetd = os.path.join(buildtop, 'tests', 'dejagnu', 't_inetd')