URL: https://github.com/SSSD/sssd/pull/5494 Author: abbra Title: #5494: pam_sss_gss: support authentication indicators Action: synchronized
To pull the PR as Git branch: git remote add ghsssd https://github.com/SSSD/sssd git fetch ghsssd pull/5494/head:pr5494 git checkout pr5494
From 49c3ce894d3cd725e97c3bb2f1006dc7a471ad44 Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy <aboko...@redhat.com> Date: Fri, 5 Feb 2021 20:36:27 +0200 Subject: [PATCH] pam_sss_gss: support authentication indicators MIT Kerberos allows to associate authentication indicators with the issued ticket based on the way how the TGT was obtained. The indicators present in the TGT then copied to service tickets. There are two ways to check the authentication indicators: - when KDC issues a service ticket, a policy at KDC side can reject the ticket issuance based on a lack of certain indicator - when a server application presented with a service ticket from a client, it can verify that this ticket contains intended authentication indicators before authorizing access from the client. Add support to validate presence of a specific (set of) authentication indicator(s) in pam_sss_gss when validating a user's TGT. This concept can be used to only allow access to a PAM service when user is in possession of a ticket obtained using some of pre-authentication mechanisms that require multiple factors: smart-cards (PKINIT), 2FA tokens (otp/radius), etc. Resolves: https://github.com/SSSD/sssd/issues/5482 Signed-off-by: Alexander Bokovoy <aboko...@redhat.com> --- src/confdb/confdb.c | 13 ++ src/confdb/confdb.h | 3 + src/config/SSSDConfig/sssdoptions.py | 2 + src/config/SSSDConfigTest.py | 6 +- src/config/cfg_rules.ini | 3 + src/config/etc/sssd.api.conf | 2 + src/db/sysdb_subdomains.c | 11 ++ src/man/pam_sss_gss.8.xml | 6 + src/man/sssd.conf.5.xml | 44 ++++++ src/responder/pam/pamsrv_gssapi.c | 218 +++++++++++++++++++++++++++ 10 files changed, 306 insertions(+), 2 deletions(-) diff --git a/src/confdb/confdb.c b/src/confdb/confdb.c index befcfff2db..4f00034044 100644 --- a/src/confdb/confdb.c +++ b/src/confdb/confdb.c @@ -1603,6 +1603,19 @@ static int confdb_get_domain_internal(struct confdb_ctx *cdb, } } + tmp = ldb_msg_find_attr_as_string(res->msgs[0], + CONFDB_PAM_GSSAPI_INDICATORS_MAP, + NULL); + if (tmp != NULL) { + ret = split_on_separator(domain, tmp, ',', true, true, + &domain->gssapi_indicators_map, NULL); + if (ret != 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot parse %s\n", CONFDB_PAM_GSSAPI_INDICATORS_MAP); + goto done; + } + } + domain->has_views = false; domain->view_name = NULL; diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index 036f9ecadf..a2be227ddd 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -146,6 +146,7 @@ #define CONFDB_PAM_INITGROUPS_SCHEME "pam_initgroups_scheme" #define CONFDB_PAM_GSSAPI_SERVICES "pam_gssapi_services" #define CONFDB_PAM_GSSAPI_CHECK_UPN "pam_gssapi_check_upn" +#define CONFDB_PAM_GSSAPI_INDICATORS_MAP "pam_gssapi_indicators_map" /* SUDO */ #define CONFDB_SUDO_CONF_ENTRY "config/sudo" @@ -437,6 +438,8 @@ struct sss_domain_info { /* List of PAM services that are allowed to authenticate with GSSAPI. */ char **gssapi_services; char *gssapi_check_upn; /* true | false | NULL */ + /* List of indicators associated with the specific PAM service */ + char **gssapi_indicators_map; }; /** diff --git a/src/config/SSSDConfig/sssdoptions.py b/src/config/SSSDConfig/sssdoptions.py index fb9a9aa43c..5d9946ba8f 100644 --- a/src/config/SSSDConfig/sssdoptions.py +++ b/src/config/SSSDConfig/sssdoptions.py @@ -106,6 +106,8 @@ def __init__(self): 'pam_initgroups_scheme' : _('When shall the PAM responder force an initgroups request'), 'pam_gssapi_services' : _('List of PAM services that are allowed to authenticate with GSSAPI.'), 'pam_gssapi_check_upn' : _('Whether to match authenticated UPN with target user'), + 'pam_gssapi_indicators_map' : _('List of pairs <PAM service>:<authentication indicator> that ' + 'must be enforced for PAM access with GSSAPI authentication'), # [sudo] 'sudo_timed': _('Whether to evaluate the time-based attributes in sudo rules'), diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py index 6a95e63dd1..04c4b35baa 100755 --- a/src/config/SSSDConfigTest.py +++ b/src/config/SSSDConfigTest.py @@ -655,7 +655,8 @@ def testListOptions(self): 'cached_auth_timeout', 'auto_private_groups', 'pam_gssapi_services', - 'pam_gssapi_check_upn'] + 'pam_gssapi_check_upn', + 'pam_gssapi_indicators_map'] self.assertTrue(type(options) == dict, "Options should be a dictionary") @@ -1036,7 +1037,8 @@ def testRemoveProvider(self): 'cached_auth_timeout', 'auto_private_groups', 'pam_gssapi_services', - 'pam_gssapi_check_upn'] + 'pam_gssapi_check_upn', + 'pam_gssapi_indicators_map'] self.assertTrue(type(options) == dict, "Options should be a dictionary") diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini index 49ceb9a2ce..bf2d03b824 100644 --- a/src/config/cfg_rules.ini +++ b/src/config/cfg_rules.ini @@ -141,6 +141,7 @@ option = p11_uri option = pam_initgroups_scheme option = pam_gssapi_services option = pam_gssapi_check_upn +option = pam_gssapi_indicators_map [rule/allowed_sudo_options] validator = ini_allowed_options @@ -441,6 +442,7 @@ option = re_expression option = auto_private_groups option = pam_gssapi_services option = pam_gssapi_check_upn +option = pam_gssapi_indicators_map #Entry cache timeouts option = entry_cache_user_timeout @@ -838,6 +840,7 @@ option = use_fully_qualified_names option = auto_private_groups option = pam_gssapi_services option = pam_gssapi_check_upn +option = pam_gssapi_indicators_map [rule/sssd_checks] validator = sssd_checks diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf index d3cad73809..49ced63859 100644 --- a/src/config/etc/sssd.api.conf +++ b/src/config/etc/sssd.api.conf @@ -82,6 +82,7 @@ p11_uri = str, None, false pam_initgroups_scheme = str, None, false pam_gssapi_services = str, None, false pam_gssapi_check_upn = bool, None, false +pam_gssapi_indicators_map = str, None, false [sudo] # sudo service @@ -203,6 +204,7 @@ re_expression = str, None, false auto_private_groups = str, None, false pam_gssapi_services = str, None, false pam_gssapi_check_upn = bool, None, false +pam_gssapi_indicators_map = str, None, false #Entry cache timeouts entry_cache_user_timeout = int, None, false diff --git a/src/db/sysdb_subdomains.c b/src/db/sysdb_subdomains.c index c0a676d491..675d41c354 100644 --- a/src/db/sysdb_subdomains.c +++ b/src/db/sysdb_subdomains.c @@ -273,6 +273,17 @@ check_subdom_config_file(struct confdb_ctx *confdb, goto done; } + /* allow to set pam_gssapi_indicators_map */ + ret = confdb_get_string_as_list(confdb, subdomain, sd_conf_path, + CONFDB_PAM_GSSAPI_INDICATORS_MAP, + &subdomain->gssapi_indicators_map); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get %s option for the subdomain: %s\n", + CONFDB_PAM_GSSAPI_INDICATORS_MAP, subdomain->name); + goto done; + } + /* case_sensitive=Preserving */ ret = confdb_get_string(confdb, tmp_ctx, sd_conf_path, CONFDB_DOMAIN_CASE_SENSITIVE, NULL, diff --git a/src/man/pam_sss_gss.8.xml b/src/man/pam_sss_gss.8.xml index ce5b11bff0..da89d27e02 100644 --- a/src/man/pam_sss_gss.8.xml +++ b/src/man/pam_sss_gss.8.xml @@ -70,6 +70,12 @@ <manvolnum>5</manvolnum> </citerefentry> for more details on these options. </para> + <para> + Some Kerberos deployments allow to assocate authentication indicators with a particular pre-authentication method used to obtain the ticket granting ticket by the user. <command>pam_sss_gss.so</command> allows to enforce presence of authentication indicators in the service tickets before a particular PAM service can be accessed. + </para> + <para> + If <option>pam_gssapi_indicators_map</option> is set in the [pam] or domain section of sssd.conf, then SSSD will perform a check of the presence of any configured indicators in the service ticket. + </para> </refsect1> <refsect1 id='options'> diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index 3ac1642782..f5eaa543e9 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -1771,6 +1771,50 @@ pam_gssapi_services = sudo, sudo-i </para> </listitem> </varlistentry> + <varlistentry> + <term>pam_gssapi_indicators_map</term> + <listitem> + <para> + Comma separated list of authentication indicators required to be present in a Kerberos ticket to access a PAM service that is allowed to try GSSAPI authentication using pam_sss_gss.so module. + </para> + <para> + Each element of the list can be either authentication indicator name or a pair + <quote>service:indicator</quote>. Indicators not prefixed with the PAM service name will be required to be present to access any PAM service configured to be used with <option>pam_gssapi_services</option>. + </para> + <para> + Note: This option can also be set per-domain which + overwrites the value in [pam] section. It can also + be set for trusted domain which overwrites the value + in the domain section. + </para> + <para> + Following authentication indicators are supported by IPA Kerberos deployments: + <itemizedlist> + <listitem> + <para>pkinit -- pre-authentication using X.509 certificates -- whether stored in files or on smart cards.</para> + </listitem> + <listitem> + <para>hardened -- SPAKE pre-authentication or any pre-authentication wrapped in a FAST channel.</para> + </listitem> + <listitem> + <para>radius -- pre-authentication with the help of a RADIUS server.</para> + </listitem> + <listitem> + <para>otp -- pre-authentication using integrated two-factor authentication (2FA or one-time password, OTP) in IPA.</para> + </listitem> + </itemizedlist> + </para> + <para> + Example: to require access to SUDO services only for users which obtained their Kerberos tickets with a smart card-based pre-authentication (PKINIT), set + <programlisting> +pam_gssapi_indicators_map = sudo:pkinit, sudo-i:pkinit + </programlisting> + </para> + <para> + Default: not set (use of authentication indicators is not required) + </para> + </listitem> + </varlistentry> </variablelist> </refsect2> diff --git a/src/responder/pam/pamsrv_gssapi.c b/src/responder/pam/pamsrv_gssapi.c index 2d05c7888a..dd02acc03b 100644 --- a/src/responder/pam/pamsrv_gssapi.c +++ b/src/responder/pam/pamsrv_gssapi.c @@ -18,12 +18,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <asm-generic/errno-base.h> #include <errno.h> #include <gssapi.h> #include <gssapi/gssapi_ext.h> #include <gssapi/gssapi_krb5.h> #include <stdint.h> #include <stdlib.h> +#include <string.h> #include <talloc.h> #include <ldb.h> @@ -83,6 +85,88 @@ static bool pam_gssapi_should_check_upn(struct pam_ctx *pam_ctx, return pam_ctx->gssapi_check_upn; } +static int pam_gssapi_check_indicators(struct pam_ctx *pam_ctx, + struct sss_domain_info *domain, + const char *pam_service, + char **indicators) +{ + char *authind = NULL; + size_t pam_len = strlen(pam_service); + char **map = domain->gssapi_indicators_map; + int res; + + authind = talloc_strdup(pam_ctx, ""); + if (authind == NULL) { + return ENOMEM; + } + + for (int i = 0; map[i]; i++) { + if (!strchr(map[i], ':')) { + authind = talloc_asprintf_append(authind, "%s ", map[i]); + if (authind == NULL) { + /* Since we allocate on pam_ctx, caller will free it */ + return ENOMEM; + } + continue; + } + + res = strncmp(map[i], pam_service, pam_len); + if (res == 0) { + if (strlen(map[i]) > pam_len) { + if (map[i][pam_len] != ':') { + /* different PAM service, skip it */ + continue; + } + + authind = talloc_asprintf_append(authind, "%s ", + map[i] + (pam_len + 1)); + if (authind == NULL) { + /* Since we allocate on pam_ctx, caller will free it */ + return ENOMEM; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Invalid value for %s: [%s]\n", + CONFDB_PAM_GSSAPI_INDICATORS_MAP, map[i]); + talloc_free(authind); + return EINVAL; + } + } + } + + res = ENOENT; + if (authind != NULL) { + if (authind[0] != '\0') { + /* trim a space after the final indicator + * to prevent split_on_separator() to fail */ + authind[strlen(authind) - 1] = '\0'; + } + res = split_on_separator(pam_ctx, authind, ' ', true, true, + &map, NULL); + if (res != 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot parse list of indicators: [%s]\n", authind); + talloc_free(authind); + return EINVAL; + } + + talloc_free(authind); + for (int i = 0; indicators[i]; i++) { + res = string_in_list(indicators[i], map, true); + if (res != 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "indicator [%s] is allowed for PAM service [%s]\n", + indicators[i], pam_service); + talloc_free(map); + return EOK; + } + } + + res = EPERM; + talloc_free(map); + } + return res; +} + static bool pam_gssapi_allowed(struct pam_ctx *pam_ctx, struct sss_domain_info *domain, const char *service) @@ -385,12 +469,130 @@ static char *gssapi_get_name(TALLOC_CTX *mem_ctx, gss_name_t gss_name) return exported; } +#define AUTH_INDICATORS_TAG "auth-indicators" + +static char **gssapi_get_indicators(TALLOC_CTX *mem_ctx, gss_name_t gss_name) +{ + gss_buffer_set_t attrs = GSS_C_NO_BUFFER_SET; + int is_mechname; + OM_uint32 major; + OM_uint32 minor; + char *exported = NULL; + char **map = NULL; + int res; + + major = gss_inquire_name(&minor, gss_name, &is_mechname, NULL, &attrs); + if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to inquire name\n"); + return NULL; + } + + if (attrs == GSS_C_NO_BUFFER_SET) { + DEBUG(SSSDBG_TRACE_FUNC, "No krb5 attributes in the ticket\n"); + return NULL; + } + + exported = talloc_strdup(mem_ctx, ""); + if (exported == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to pre-allocate indicators\n"); + return NULL; + } + + for (int i = 0; i < attrs->count; i++) { + gss_buffer_desc value; + gss_buffer_desc display_value; + int authenticated = 0; + int complete = 0; + int more = -1; + + /* skip anything but auth-indicators */ + if (strncmp(AUTH_INDICATORS_TAG, attrs->elements[i].value, + sizeof(AUTH_INDICATORS_TAG) - 1) != 0) + continue; + + /* retrieve all indicators */ + while (more != 0) { + value.value = NULL; + display_value.value = NULL; + + major = gss_get_name_attribute(&minor, gss_name, + &attrs->elements[i], + &authenticated, + &complete, &value, + &display_value, + &more); + if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to retrieve an attribute\n"); + gss_release_buffer(&minor, &value); + gss_release_buffer(&minor, &display_value); + TALLOC_FREE(exported); + (void)gss_release_buffer_set(&minor, &attrs); + return NULL; + } + + if ((value.value != NULL) && authenticated) { + DEBUG(SSSDBG_TRACE_FUNC, + "attribute's [%.*s] value [%.*s] authenticated\n", + (int) attrs->elements[i].length, + (char*) attrs->elements[i].value, + (int) value.length, + (char*) value.value); + exported = talloc_asprintf_append(exported, "%.*s ", + (int) value.length, + (char*) value.value); + } + + if (exported == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to collect an attribute value\n"); + (void)gss_release_buffer(&minor, &value); + (void)gss_release_buffer(&minor, &display_value); + (void)gss_release_buffer_set(&minor, &attrs); + return NULL; + } + (void)gss_release_buffer(&minor, &value); + (void)gss_release_buffer(&minor, &display_value); + } + } + + (void)gss_release_buffer_set(&minor, &attrs); + + if ((exported != NULL) && (exported[0] != '\0')) { + /* trim a space after the final indicator + * to prevent split_on_separator() to fail */ + exported[strlen(exported) - 1] = '\0'; + } else { + /* empty or NULL list */ + talloc_free(exported); + return NULL; + } + + res = split_on_separator(mem_ctx, exported, ' ', true, true, + &map, NULL); + if (res != 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot parse list of indicators: [%s]\n", exported); + talloc_free(exported); + return NULL; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "authentication indicators: [%s]\n", + exported); + } + + talloc_free(exported); + return map; +} + + struct gssapi_state { struct cli_ctx *cli_ctx; struct sss_domain_info *domain; const char *username; char *authenticated_upn; + char **auth_indicators; bool established; gss_ctx_id_t ctx; }; @@ -568,6 +770,8 @@ gssapi_handshake(struct gssapi_state *state, DEBUG(SSSDBG_TRACE_FUNC, "Security context established with [%s]\n", state->authenticated_upn); + state->auth_indicators = gssapi_get_indicators(state, client_name); + state->established = true; ret = EOK; @@ -699,6 +903,20 @@ pam_cmd_gssapi_sec_ctx(struct cli_ctx *cli_ctx) goto done; } + if (domain->gssapi_indicators_map != NULL) { + ret = pam_gssapi_check_indicators(pam_ctx, domain, + pam_service, + state->auth_indicators); + DEBUG(SSSDBG_TRACE_FUNC, + "Check if acquired service ticket has req. indicators: %d\n", + ret); + if ((ret == EPERM) || (ret == ENOMEM)) { + /* skip further checks if denied or no memory, + * EINVAL means the check is not applicable */ + goto done; + } + } + if (!pam_gssapi_should_check_upn(pam_ctx, domain)) { /* We are done. */ goto done;
_______________________________________________ sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org Fedora Code of Conduct: https://docs.fedoraproject.org/en-US/project/code-of-conduct/ List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines List Archives: https://lists.fedorahosted.org/archives/list/sssd-devel@lists.fedorahosted.org