From 855201c70f69f2b1dbcb3faef780fbdb84354f18 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Sun, 26 Mar 2017 18:28:41 +0200 Subject: [PATCH 67/72] PAM: Add application services Related to: https://pagure.io/SSSD/sssd/issue/3310 Adds a new PAM responder option 'pam_app_services'. This option can hold a list of PAM services that are allowed to contact the application non-POSIX domains. These services are NOT allowed to contact any of the POSIX domains. Reviewed-by: Sumit Bose --- src/confdb/confdb.h | 1 + src/config/SSSDConfig/__init__.py.in | 1 + src/config/cfg_rules.ini | 1 + src/config/etc/sssd.api.conf | 1 + src/man/sssd.conf.5.xml | 12 +++ src/responder/pam/pamsrv.c | 33 +++++++ src/responder/pam/pamsrv.h | 5 ++ src/responder/pam/pamsrv_cmd.c | 26 +++++- src/tests/cmocka/test_pam_srv.c | 167 ++++++++++++++++++++++++++++++++++- 9 files changed, 241 insertions(+), 6 deletions(-) diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index 5a8d377c312f641f544b1c7cf38826192462ea3c..8719c239362b371fcdb1b78956bcddde871f141b 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -129,6 +129,7 @@ #define CONFDB_PAM_CERT_AUTH "pam_cert_auth" #define CONFDB_PAM_CERT_DB_PATH "pam_cert_db_path" #define CONFDB_PAM_P11_CHILD_TIMEOUT "p11_child_timeout" +#define CONFDB_PAM_APP_SERVICES "pam_app_services" /* SUDO */ #define CONFDB_SUDO_CONF_ENTRY "config/sudo" diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in index 806611b6076048c08ce08c772dbd3cea5fdd656c..211338778e81c1c60ffb3cdbc67c9619343d7798 100644 --- a/src/config/SSSDConfig/__init__.py.in +++ b/src/config/SSSDConfig/__init__.py.in @@ -102,6 +102,7 @@ option_strings = { 'pam_cert_auth' : _('Allow certificate based/Smartcard authentication.'), 'pam_cert_db_path' : _('Path to certificate databse with PKCS#11 modules.'), 'p11_child_timeout' : _('How many seconds will pam_sss wait for p11_child to finish'), + 'pam_app_services' : _('Which PAM services are permitted to contact application domains'), # [sudo] 'sudo_timed' : _('Whether to evaluate the time-based attributes in sudo rules'), diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini index 8fd2d2c5236246394353a88c50d1510bd6233f77..1a749db754cedd87f263f7ae596d6f8238bb4357 100644 --- a/src/config/cfg_rules.ini +++ b/src/config/cfg_rules.ini @@ -119,6 +119,7 @@ option = pam_account_locked_message option = pam_cert_auth option = pam_cert_db_path option = p11_child_timeout +option = pam_app_services [rule/allowed_sudo_options] validator = ini_allowed_options diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf index a38b24208f89e4502e41625c540ea9958d5bbffe..a1a0c2992925a4c7df86832117eec2a0cf7894c9 100644 --- a/src/config/etc/sssd.api.conf +++ b/src/config/etc/sssd.api.conf @@ -73,6 +73,7 @@ pam_account_locked_message = str, None, false pam_cert_auth = bool, None, false pam_cert_db_path = str, None, false p11_child_timeout = int, None, false +pam_app_services = str, None, false [sudo] # sudo service diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index 8294793c765bfa6bf481693c7d7f206950454681..c4e30396f16c40db37af2f56ac218b6e37201ef7 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -1325,6 +1325,18 @@ pam_account_locked_message = Account locked, please contact help desk. + + pam_app_services (string) + + + Which PAM services are permitted to contact + domains of type application + + + Default: Not set + + + diff --git a/src/responder/pam/pamsrv.c b/src/responder/pam/pamsrv.c index ab3f4545520f3fcb2492a6089a039c46f0fb847f..79470823d18138da6ef9235e6336a3220ead1797 100644 --- a/src/responder/pam/pamsrv.c +++ b/src/responder/pam/pamsrv.c @@ -166,6 +166,32 @@ done: return ret; } +static errno_t get_app_services(struct pam_ctx *pctx) +{ + errno_t ret; + + ret = confdb_get_string_as_list(pctx->rctx->cdb, pctx, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_APP_SERVICES, + &pctx->app_services); + if (ret == ENOENT) { + pctx->app_services = talloc_zero_array(pctx, char *, 1); + if (pctx->app_services == NULL) { + return ENOMEM; + } + /* Allocating an empty array makes it easier for the consumer + * to iterate over it + */ + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot read "CONFDB_PAM_APP_SERVICES" [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} + static int pam_process_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct confdb_ctx *cdb, @@ -219,6 +245,13 @@ static int pam_process_init(TALLOC_CTX *mem_ctx, goto done; } + ret = get_app_services(pctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "get_app_services failed: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + /* Enable automatic reconnection to the Data Provider */ /* FIXME: "retries" is too generic, either get it from a global config diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h index b3eb56441048ecdba82866a95f1d6d6d5e786c60..b569748fe2a2005cee5df34bef55e803175492a9 100644 --- a/src/responder/pam/pamsrv.h +++ b/src/responder/pam/pamsrv.h @@ -26,6 +26,7 @@ #include "util/util.h" #include "sbus/sssd_dbus.h" #include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" struct pam_auth_req; @@ -42,6 +43,9 @@ struct pam_ctx { char **public_domains; int public_domains_count; + /* What services are permitted to access application domains */ + char **app_services; + bool cert_auth; int p11_child_debug_fd; char *nss_db; @@ -54,6 +58,7 @@ struct pam_auth_dp_req { struct pam_auth_req { struct cli_ctx *cctx; struct sss_domain_info *domain; + enum cache_req_dom_type req_dom_type; struct pam_data *pd; diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c index fa6d2cc10fe1404196f9d9221a469d7a9a768211..f2b3c74b483e527932dda42279d14a9ac184b475 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -1161,6 +1161,25 @@ static bool is_domain_public(char *name, return false; } +static enum cache_req_dom_type +get_domain_request_type(struct pam_auth_req *preq, + struct pam_ctx *pctx) +{ + enum cache_req_dom_type req_dom_type; + + /* By default, only POSIX domains are to be contacted */ + req_dom_type = CACHE_REQ_POSIX_DOM; + + for (int i = 0; pctx->app_services[i]; i++) { + if (strcmp(pctx->app_services[i], preq->pd->service) == 0) { + req_dom_type = CACHE_REQ_APPLICATION_DOM; + break; + } + } + + return req_dom_type; +} + static errno_t check_cert(TALLOC_CTX *mctx, struct tevent_context *ev, struct pam_ctx *pctx, @@ -1257,6 +1276,9 @@ static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd) goto done; } + /* Determine what domain type to contact */ + preq->req_dom_type = get_domain_request_type(preq, pctx); + /* try backend first for authentication before doing local Smartcard * authentication */ if (pd->cmd != SSS_PAM_AUTHENTICATE && may_do_cert_auth(pctx, pd)) { @@ -1316,7 +1338,7 @@ static void pam_forwarder_cert_cb(struct tevent_req *req) req = cache_req_user_by_cert_send(preq, cctx->ev, cctx->rctx, pctx->rctx->ncache, 0, - CACHE_REQ_POSIX_DOM, NULL, + preq->req_dom_type, NULL, cert); if (req == NULL) { DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n"); @@ -1509,7 +1531,7 @@ static int pam_check_user_search(struct pam_auth_req *preq) preq->cctx->rctx, preq->cctx->rctx->ncache, 0, - CACHE_REQ_POSIX_DOM, + preq->req_dom_type, preq->pd->domain, data); if (!dpreq) { diff --git a/src/tests/cmocka/test_pam_srv.c b/src/tests/cmocka/test_pam_srv.c index 847419658bb983e6548722d6fa6fb22c63ee86b8..d249b8f1ea48f1c17b461c3add9e8c63774e5f88 100644 --- a/src/tests/cmocka/test_pam_srv.c +++ b/src/tests/cmocka/test_pam_srv.c @@ -186,6 +186,15 @@ struct pam_ctx *mock_pctx(TALLOC_CTX *mem_ctx) ret = sss_hash_create(pctx, 10, &pctx->id_table); assert_int_equal(ret, EOK); + /* Two NULLs so that tests can just assign a const to the first slot + * should they need it. The code iterates until first NULL anyway + */ + pctx->app_services = talloc_zero_array(pctx, char *, 2); + if (pctx->app_services == NULL) { + talloc_free(pctx); + return NULL; + } + return pctx; } @@ -495,8 +504,12 @@ int __wrap_pam_dp_send_req(struct pam_auth_req *preq, int timeout) return EOK; } -static void mock_input_pam(TALLOC_CTX *mem_ctx, const char *name, - const char *pwd, const char *fa2) +static void mock_input_pam_ex(TALLOC_CTX *mem_ctx, + const char *name, + const char *pwd, + const char *fa2, + const char *svc, + bool contact_dp) { size_t buf_size; uint8_t *m_buf; @@ -536,7 +549,10 @@ static void mock_input_pam(TALLOC_CTX *mem_ctx, const char *name, } } - pi.pam_service = "pam_test_service"; + if (svc == NULL) { + svc = "pam_test_service"; + } + pi.pam_service = svc; pi.pam_service_size = strlen(pi.pam_service) + 1; pi.pam_tty = "/dev/tty"; pi.pam_tty_size = strlen(pi.pam_tty) + 1; @@ -559,7 +575,17 @@ static void mock_input_pam(TALLOC_CTX *mem_ctx, const char *name, will_return(__wrap_sss_packet_get_body, buf_size); mock_parse_inp(name, NULL, EOK); - mock_account_recv_simple(); + if (contact_dp) { + mock_account_recv_simple(); + } +} + +static void mock_input_pam(TALLOC_CTX *mem_ctx, + const char *name, + const char *pwd, + const char *fa2) +{ + return mock_input_pam_ex(mem_ctx, name, pwd, fa2, NULL, true); } static void mock_input_pam_cert(TALLOC_CTX *mem_ctx, const char *name, @@ -2097,6 +2123,127 @@ void test_filter_response(void **state) talloc_free(pd); } +static int pam_test_setup_appsvc_posix_dom(void **state) +{ + int ret; + + ret = pam_test_setup(state); + if (ret != EOK) { + return ret; + } + + /* This config option is only read on startup, which is not executed + * in test, so we can't just pass in a param + */ + pam_test_ctx->pctx->app_services[0] = discard_const("app_svc"); + return 0; +} + +void test_appsvc_posix_dom(void **state) +{ + int ret; + + /* The domain is POSIX, the request will skip over it */ + mock_input_pam_ex(pam_test_ctx, "pamuser", NULL, NULL, "app_svc", false); + pam_test_ctx->exp_pam_status = PAM_USER_UNKNOWN; + + will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE); + will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL); + + set_cmd_cb(test_pam_user_unknown_check); + ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE, + pam_test_ctx->pam_cmds); + assert_int_equal(ret, EOK); + + ret = test_ev_loop(pam_test_ctx->tctx); + assert_int_equal(ret, EOK); +} + +void test_not_appsvc_posix_dom(void **state) +{ + int ret; + + /* A different service than the app one can authenticate against a POSIX domain */ + mock_input_pam_ex(pam_test_ctx, "pamuser", NULL, NULL, "not_app_svc", true); + + will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE); + will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL); + + set_cmd_cb(test_pam_simple_check); + ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE, + pam_test_ctx->pam_cmds); + assert_int_equal(ret, EOK); + + /* Wait until the test finishes with EOK */ + ret = test_ev_loop(pam_test_ctx->tctx); + assert_int_equal(ret, EOK); +} + +static int pam_test_setup_appsvc_app_dom(void **state) +{ + struct sss_test_conf_param dom_params[] = { + { "domain_type", "application" }, + { NULL, NULL }, /* Sentinel */ + }; + struct sss_test_conf_param pam_params[] = { + { NULL, NULL }, /* Sentinel */ + }; + struct sss_test_conf_param monitor_params[] = { + { NULL, NULL }, /* Sentinel */ + }; + + + test_pam_setup(dom_params, pam_params, monitor_params, state); + pam_test_setup_common(); + + /* This config option is only read on startup, which is not executed + * in test, so we can't just pass in a param + */ + pam_test_ctx->pctx->app_services[0] = discard_const("app_svc"); + return 0; +} + +void test_appsvc_app_dom(void **state) +{ + int ret; + + /* The domain is POSIX, the request will skip over it */ + mock_input_pam_ex(pam_test_ctx, "pamuser", NULL, NULL, "app_svc", true); + + will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE); + will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL); + + set_cmd_cb(test_pam_simple_check); + ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE, + pam_test_ctx->pam_cmds); + assert_int_equal(ret, EOK); + + /* Wait until the test finishes with EOK */ + ret = test_ev_loop(pam_test_ctx->tctx); + assert_int_equal(ret, EOK); +} + +void test_not_appsvc_app_dom(void **state) +{ + int ret; + + /* A different service than the app one can authenticate against a POSIX domain */ + mock_input_pam_ex(pam_test_ctx, "pamuser", NULL, NULL, "not_app_svc", false); + + pam_test_ctx->exp_pam_status = PAM_USER_UNKNOWN; + + will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE); + will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL); + + set_cmd_cb(test_pam_user_unknown_check); + ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE, + pam_test_ctx->pam_cmds); + assert_int_equal(ret, EOK); + + ret = test_ev_loop(pam_test_ctx->tctx); + assert_int_equal(ret, EOK); +} + int main(int argc, const char *argv[]) { int rv; @@ -2216,6 +2363,18 @@ int main(int argc, const char *argv[]) cmocka_unit_test_setup_teardown(test_filter_response, pam_test_setup, pam_test_teardown), + cmocka_unit_test_setup_teardown(test_appsvc_posix_dom, + pam_test_setup_appsvc_posix_dom, + pam_test_teardown), + cmocka_unit_test_setup_teardown(test_not_appsvc_posix_dom, + pam_test_setup_appsvc_posix_dom, + pam_test_teardown), + cmocka_unit_test_setup_teardown(test_appsvc_app_dom, + pam_test_setup_appsvc_app_dom, + pam_test_teardown), + cmocka_unit_test_setup_teardown(test_not_appsvc_app_dom, + pam_test_setup_appsvc_posix_dom, + pam_test_teardown), }; /* Set debug level to invalid value so we can deside if -d 0 was used. */ -- 2.9.3