/* * Copyright (C) 2018,2020 FreeIPA Contributors see COPYING for license */ #include #include #include #include #include "ipa_krb5.h" #include "ipa_kdb.h" #define ONE_DAY_SECONDS (24 * 60 * 60) #define JITTER_WINDOW_SECONDS (1 * 60 * 60) krb5_error_code kdcpolicy_ipakdb_initvt(krb5_context context, int maj_ver, int min_ver, krb5_plugin_vtable vtable); static void jitter(krb5_deltat baseline, krb5_deltat *lifetime_out) { krb5_deltat offset; ssize_t ret; if (baseline < JITTER_WINDOW_SECONDS) { /* A negative value here would correspond to a never-valid ticket, * which isn't the goal. */ *lifetime_out = baseline; return; } do { ret = getrandom(&offset, sizeof(offset), 0); } while (ret == -1 && errno == EINTR); if (ret < 0) { krb5_klog_syslog(LOG_INFO, "IPA kdcpolicy: getrandom failed (errno %d); skipping jitter...", errno); return; } *lifetime_out = baseline - abs(offset) % JITTER_WINDOW_SECONDS; } static krb5_error_code ipa_kdcpolicy_check_as(krb5_context context, krb5_kdcpolicy_moddata moddata, const krb5_kdc_req *request, const krb5_db_entry *client, const krb5_db_entry *server, const char *const *auth_indicators, const char **status, krb5_deltat *lifetime_out, krb5_deltat *renew_lifetime_out) { krb5_error_code kerr = 0; enum ipadb_user_auth ua; struct ipadb_e_data *ied; struct ipadb_e_pol_limits *pol_limits = NULL; int valid_auth_indicators = 0, flags = 0; krb5_db_entry *client_actual = NULL; #ifdef KRB5_KDB_FLAG_ALIAS_OK flags = KRB5_KDB_FLAG_ALIAS_OK; #endif *status = NULL; *lifetime_out = 0; *renew_lifetime_out = 0; 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 */ krb5_klog_syslog(LOG_INFO, "IPA kdcpolicy: client e_data not availble. Try fetching..."); kerr = ipadb_get_principal(context, request->client, flags, &client_actual); if (kerr != 0) { krb5_klog_syslog(LOG_ERR, "IPA kdcpolicy: ipadb_find_principal failed."); goto done; } ied = (struct ipadb_e_data *)client_actual->e_data; if (ied == NULL || ied->magic != IPA_E_DATA_MAGIC) { krb5_klog_syslog(LOG_ERR, "IPA kdcpolicy: client e_data fetching failed."); kerr = EINVAL; goto done; } } ua = ied->user_auth; /* If no mechanisms are set, or it is anonymous PKINIT, allow every auth method */ if ((ua == IPADB_USER_AUTH_NONE) || (request->kdc_options & KDC_OPT_REQUEST_ANONYMOUS)) { jitter(ONE_DAY_SECONDS, lifetime_out); kerr = 0; goto done; } /* For each auth indicator, see if it is allowed for that user */ for (int i = 0; auth_indicators[i] != NULL; i++) { const char *auth_indicator = auth_indicators[i]; if (strcmp(auth_indicator, "otp") == 0) { valid_auth_indicators++; if (!(ua & IPADB_USER_AUTH_OTP)) { *status = "OTP pre-authentication not allowed for this user."; kerr = KRB5KDC_ERR_POLICY; goto done; } 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."; kerr = KRB5KDC_ERR_POLICY; goto done; } pol_limits = &(ied->pol_limits[IPADB_USER_AUTH_IDX_RADIUS]); } else if (strcmp(auth_indicator, "pkinit") == 0) { valid_auth_indicators++; /* allow PKINIT unconditionally -- it has passed already at this * point so some certificate was useful, only apply the limits */ 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 */ if (!(ua & (IPADB_USER_AUTH_HARDENED | IPADB_USER_AUTH_PASSWORD))) { *status = "Password pre-authentication not allowed for this user."; kerr = KRB5KDC_ERR_POLICY; goto done; } pol_limits = &(ied->pol_limits[IPADB_USER_AUTH_IDX_HARDENED]); } else if (strcmp(auth_indicator, "idp") == 0) { valid_auth_indicators++; /* Allow hardened even if only password pre-auth is allowed */ if (!(ua & IPADB_USER_AUTH_IDP)) { *status = "IdP pre-authentication not allowed for this user."; kerr = KRB5KDC_ERR_POLICY; goto done; } pol_limits = &(ied->pol_limits[IPADB_USER_AUTH_IDX_IDP]); } else if (strcmp(auth_indicator, "passkey") == 0) { valid_auth_indicators++; /* Allow hardened even if only password pre-auth is allowed */ if (!(ua & IPADB_USER_AUTH_PASSKEY)) { *status = "Passkey pre-authentication not allowed for this user."; kerr = KRB5KDC_ERR_POLICY; goto done; } pol_limits = &(ied->pol_limits[IPADB_USER_AUTH_IDX_PASSKEY]); } } /* There is no auth indicator assigned for non-hardened password authentication * so we assume password is used when no supported indicator exists */ if (!valid_auth_indicators) { if (!(ua & IPADB_USER_AUTH_PASSWORD)) { *status = "Non-hardened password authentication not allowed for this user."; kerr = KRB5KDC_ERR_POLICY; goto done; } } /* If there were policy limits associated with the authentication indicators, * apply them */ if (pol_limits != NULL) { if (pol_limits->max_life != 0) { jitter(pol_limits->max_life, lifetime_out); } else { jitter(client->max_life, lifetime_out); } if (pol_limits->max_renewable_life != 0) { *renew_lifetime_out = pol_limits->max_renewable_life; } } done: ipadb_free_principal(context, client_actual); return kerr; } static krb5_error_code ipa_kdcpolicy_check_tgs(krb5_context context, krb5_kdcpolicy_moddata moddata, const krb5_kdc_req *request, const krb5_db_entry *server, const krb5_ticket *ticket, const char *const *auth_indicators, const char **status, krb5_deltat *lifetime_out, krb5_deltat *renew_lifetime_out) { *status = NULL; *lifetime_out = 0; *renew_lifetime_out = 0; return 0; } krb5_error_code kdcpolicy_ipakdb_initvt(krb5_context context, int maj_ver, int min_ver, krb5_plugin_vtable vtable) { krb5_kdcpolicy_vtable vt; if (maj_ver != 1) return KRB5_PLUGIN_VER_NOTSUPP; vt = (krb5_kdcpolicy_vtable)vtable; vt->name = "ipakdb"; vt->init = NULL; vt->fini = NULL; vt->check_as = ipa_kdcpolicy_check_as; vt->check_tgs = ipa_kdcpolicy_check_tgs; return 0; }