From 855201c70f69f2b1dbcb3faef780fbdb84354f18 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
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 <sbose@redhat.com>
---
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.
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>pam_app_services (string)</term>
+ <listitem>
+ <para>
+ Which PAM services are permitted to contact
+ domains of type <quote>application</quote>
+ </para>
+ <para>
+ Default: Not set
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect2>
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