sssd/0041-AD-add-task-to-renew-t...

576 lines
21 KiB
Diff

From 70a669646ed841048346b451741e972a0ada703d Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Tue, 12 Jan 2016 11:05:02 +0100
Subject: [PATCH 41/49] AD: add task to renew the machine account password if
needed
AD expects its clients to renew the machine account password on a
regular basis, be default every 30 days. Even if a client does not renew
the password it might not cause issues because AD does not enforce the
renewal. But the password age might be used to identify unused machine
accounts in large environments which might get disabled or deleted
automatically.
With this patch SSSD calls an external program to check the age of the
machine account password and renew it if needed. Currently 'adcli' is
used as external program which is able to renew the password since
version 0.8.0.
Resolves https://fedorahosted.org/sssd/ticket/1041
Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
(cherry picked from commit 5f7cd30c865046a7ea69944f7e07c85b4c43465a)
---
Makefile.am | 1 +
src/config/SSSDConfig/__init__.py.in | 2 +
src/config/etc/sssd.api.d/sssd-ad.conf | 2 +
src/man/sssd-ad.5.xml | 33 +++
src/providers/ad/ad_common.h | 5 +
src/providers/ad/ad_init.c | 7 +
src/providers/ad/ad_machine_pw_renewal.c | 372 +++++++++++++++++++++++++++++++
src/providers/ad/ad_opts.c | 2 +
src/util/util_errors.c | 1 +
src/util/util_errors.h | 1 +
10 files changed, 426 insertions(+)
create mode 100644 src/providers/ad/ad_machine_pw_renewal.c
diff --git a/Makefile.am b/Makefile.am
index 1c0b1aada9804b2ef35a09cf1b7bf5e9c65ee4e5..a9099c07fcfe54a88bd56129364dde5262e901ed 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3061,6 +3061,7 @@ libsss_ad_la_SOURCES = \
src/providers/ad/ad_common.h \
src/providers/ad/ad_init.c \
src/providers/ad/ad_dyndns.c \
+ src/providers/ad/ad_machine_pw_renewal.c \
src/providers/ad/ad_id.c \
src/providers/ad/ad_id.h \
src/providers/ad/ad_access.c \
diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in
index 2cb857013fe4bddfd2e79e589d3ba9721dc3ca4f..b4a6fcb0d37469e1dda85eda95fd80825697902c 100644
--- a/src/config/SSSDConfig/__init__.py.in
+++ b/src/config/SSSDConfig/__init__.py.in
@@ -199,6 +199,8 @@ option_strings = {
'ad_gpo_map_deny' : _('PAM service names for which GPO-based access is always denied'),
'ad_gpo_default_right' : _('Default logon right (or permit/deny) to use for unmapped PAM service names'),
'ad_site' : _('a particular site to be used by the client'),
+ 'ad_maximum_machine_account_password_age' : _('Maximum age in days before the machine account password should be renewed'),
+ 'ad_machine_account_password_renewal_opts' : _('Option for tuing the machine account renewal task'),
# [provider/krb5]
'krb5_kdcip' : _('Kerberos server address'),
diff --git a/src/config/etc/sssd.api.d/sssd-ad.conf b/src/config/etc/sssd.api.d/sssd-ad.conf
index 5eb546caac913b839112a70bd81dbde2c7ff2d9f..0ea73d14112d1c7cf7a6d4cbda0d2b2e53a3a7be 100644
--- a/src/config/etc/sssd.api.d/sssd-ad.conf
+++ b/src/config/etc/sssd.api.d/sssd-ad.conf
@@ -17,6 +17,8 @@ ad_gpo_map_permit = str, None, false
ad_gpo_map_deny = str, None, false
ad_gpo_default_right = str, None, false
ad_site = str, None, false
+ad_maximum_machine_account_password_age = int, None, false
+ad_machine_account_password_renewal_opts = str, None, false
ldap_uri = str, None, false
ldap_backup_uri = str, None, false
ldap_search_base = str, None, false
diff --git a/src/man/sssd-ad.5.xml b/src/man/sssd-ad.5.xml
index 173fb93009f66c2c83ab87ff5ca900fc10cbf5e8..4280eac5f4594b26d158a0ea58622f9fe7beb53e 100644
--- a/src/man/sssd-ad.5.xml
+++ b/src/man/sssd-ad.5.xml
@@ -719,6 +719,39 @@ ad_gpo_map_deny = +my_pam_service
</varlistentry>
<varlistentry>
+ <term>ad_maximum_machine_account_password_age (integer)</term>
+ <listitem>
+ <para>
+ SSSD will check once a day if the machine account
+ password is older than the given age in days and try
+ to renew it. A value of 0 will disable the renewal
+ attempt.
+ </para>
+ <para>
+ Default: 30 days
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ad_machine_account_password_renewal_opts (string)</term>
+ <listitem>
+ <para>
+ This option should only be used to test the machine
+ account renewal task. The option expect 2 integers
+ seperated by a colon (':'). The first integer
+ defines the interval in seconds how often the task
+ is run. The second specifies the inital timeout in
+ seconds before the task is run for the first time
+ after startup.
+ </para>
+ <para>
+ Default: 86400:750 (24h and 15m)
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term>dyndns_update (boolean)</term>
<listitem>
<para>
diff --git a/src/providers/ad/ad_common.h b/src/providers/ad/ad_common.h
index 2dd4175487cd36215dad1aaa9111e316a1fc3a0a..5bb2e52d402e4279fdc60d4ab58afd2292358487 100644
--- a/src/providers/ad/ad_common.h
+++ b/src/providers/ad/ad_common.h
@@ -62,6 +62,8 @@ enum ad_basic_opt {
AD_GPO_DEFAULT_RIGHT,
AD_SITE,
AD_KRB5_CONFD_PATH,
+ AD_MAXIMUM_MACHINE_ACCOUNT_PASSWORD_AGE,
+ AD_MACHINE_ACCOUNT_PASSWORD_RENEWAL_OPTS,
AD_OPTS_BASIC /* opts counter */
};
@@ -180,4 +182,7 @@ int ad_autofs_init(struct be_ctx *be_ctx,
struct bet_ops **ops,
void **pvt_data);
+errno_t ad_machine_account_password_renewal_init(struct be_ctx *be_ctx,
+ struct ad_options *ad_opts);
+
#endif /* AD_COMMON_H_ */
diff --git a/src/providers/ad/ad_init.c b/src/providers/ad/ad_init.c
index 72ce5536b0f0f69a530bda0ffc41ae93180c1a94..e40fb6f1d0eabae45581969f1ff73c8cf302fb4c 100644
--- a/src/providers/ad/ad_init.c
+++ b/src/providers/ad/ad_init.c
@@ -308,6 +308,13 @@ sssm_ad_id_init(struct be_ctx *bectx,
"will not work [%d]: %s\n", ret, strerror(ret));
}
+ ret = ad_machine_account_password_renewal_init(bectx, ad_options);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Cannot setup task for machine account "
+ "password renewal.\n");
+ goto done;
+ }
+
*ops = &ad_id_ops;
*pvt_data = ad_ctx;
diff --git a/src/providers/ad/ad_machine_pw_renewal.c b/src/providers/ad/ad_machine_pw_renewal.c
new file mode 100644
index 0000000000000000000000000000000000000000..e42c700e7aa3cf9a45acee025e36899b36642dad
--- /dev/null
+++ b/src/providers/ad/ad_machine_pw_renewal.c
@@ -0,0 +1,372 @@
+/*
+ SSSD
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2016 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "util/util.h"
+#include "util/strtonum.h"
+#include "providers/dp_ptask.h"
+#include "providers/ad/ad_common.h"
+
+#ifndef RENEWAL_PROG_PATH
+#define RENEWAL_PROG_PATH "/usr/sbin/adcli"
+#endif
+
+struct renewal_data {
+ char *prog_path;
+ const char **extra_args;
+};
+
+static errno_t get_adcli_extra_args(const char *ad_domain,
+ const char *ad_hostname,
+ const char *ad_keytab,
+ size_t pw_lifetime_in_days,
+ size_t period,
+ size_t initial_delay,
+ struct renewal_data *renewal_data)
+{
+ const char **args;
+ size_t c = 0;
+
+ if (ad_domain == NULL || ad_hostname == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing AD domain or hostname.\n");
+ return EINVAL;
+ }
+
+ renewal_data->prog_path = talloc_strdup(renewal_data, RENEWAL_PROG_PATH);
+ if (renewal_data->prog_path == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+ return ENOMEM;
+ }
+
+ args = talloc_array(renewal_data, const char *, 7);
+ if (args == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n");
+ return ENOMEM;
+ }
+
+ /* extra_args are added in revers order */
+ args[c++] = talloc_asprintf(args, "--computer-password-lifetime=%zu",
+ pw_lifetime_in_days);
+ args[c++] = talloc_asprintf(args, "--host-fqdn=%s", ad_hostname);
+ if (ad_keytab != NULL) {
+ args[c++] = talloc_asprintf(args, "--host-keytab=%s", ad_keytab);
+ }
+ args[c++] = talloc_asprintf(args, "--domain=%s", ad_domain);
+ if (DEBUG_IS_SET(SSSDBG_TRACE_LIBS)) {
+ args[c++] = talloc_strdup(args, "--verbose");
+ }
+ args[c++] = talloc_strdup(args, "update");
+ args[c] = NULL;
+
+ do {
+ if (args[--c] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "talloc failed while copying arguments.\n");
+ talloc_free(args);
+ return ENOMEM;
+ }
+ } while (c != 0);
+
+ renewal_data->extra_args = args;
+
+ return EOK;
+}
+
+struct renewal_state {
+ int child_status;
+ struct sss_child_ctx_old *child_ctx;
+ struct tevent_timer *timeout_handler;
+ struct tevent_context *ev;
+
+ int write_to_child_fd;
+ int read_from_child_fd;
+};
+
+static void ad_machine_account_password_renewal_done(struct tevent_req *subreq);
+static void
+ad_machine_account_password_renewal_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt);
+
+static struct tevent_req *
+ad_machine_account_password_renewal_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct be_ctx *be_ctx,
+ struct be_ptask *be_ptask,
+ void *pvt)
+{
+ struct renewal_data *renewal_data;
+ struct renewal_state *state;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ pid_t child_pid;
+ struct timeval tv;
+ int pipefd_to_child[2];
+ int pipefd_from_child[2];
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct renewal_state);
+ if (req == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n");
+ return NULL;
+ }
+
+ renewal_data = talloc_get_type(pvt, struct renewal_data);
+
+ state->ev = ev;
+ state->child_status = EFAULT;
+ state->read_from_child_fd = -1;
+ state->write_to_child_fd = -1;
+
+ ret = pipe(pipefd_from_child);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "pipe failed [%d][%s].\n", ret, strerror(ret));
+ goto done;
+ }
+ ret = pipe(pipefd_to_child);
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "pipe failed [%d][%s].\n", ret, strerror(ret));
+ goto done;
+ }
+
+ child_pid = fork();
+ if (child_pid == 0) { /* child */
+ ret = exec_child_ex(state, pipefd_to_child, pipefd_from_child,
+ renewal_data->prog_path, -1,
+ renewal_data->extra_args, true,
+ STDIN_FILENO, STDERR_FILENO);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Could not exec renewal child: [%d][%s].\n",
+ ret, strerror(ret));
+ goto done;
+ }
+ } else if (child_pid > 0) { /* parent */
+
+ state->read_from_child_fd = pipefd_from_child[0];
+ close(pipefd_from_child[1]);
+ sss_fd_nonblocking(state->read_from_child_fd);
+
+ state->write_to_child_fd = pipefd_to_child[1];
+ close(pipefd_to_child[0]);
+ sss_fd_nonblocking(state->write_to_child_fd);
+
+ /* Set up SIGCHLD handler */
+ ret = child_handler_setup(ev, child_pid, NULL, NULL, &state->child_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n",
+ ret, sss_strerror(ret));
+ ret = ERR_RENEWAL_CHILD;
+ goto done;
+ }
+
+ /* Set up timeout handler */
+ tv = tevent_timeval_current_ofs(be_ptask_get_timeout(be_ptask), 0);
+ state->timeout_handler = tevent_add_timer(ev, req, tv,
+ ad_machine_account_password_renewal_timeout,
+ req);
+ if(state->timeout_handler == NULL) {
+ ret = ERR_RENEWAL_CHILD;
+ goto done;
+ }
+
+ subreq = read_pipe_send(state, ev, state->read_from_child_fd);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n");
+ ret = ERR_RENEWAL_CHILD;
+ goto done;
+ }
+ tevent_req_set_callback(subreq,
+ ad_machine_account_password_renewal_done, req);
+
+ /* Now either wait for the timeout to fire or the child
+ * to finish
+ */
+ } else { /* error */
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void ad_machine_account_password_renewal_done(struct tevent_req *subreq)
+{
+ uint8_t *buf;
+ ssize_t buf_len;
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct renewal_state *state = tevent_req_data(req, struct renewal_state);
+ int ret;
+
+ talloc_zfree(state->timeout_handler);
+
+ ret = read_pipe_recv(subreq, state, &buf, &buf_len);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DEBUG(SSSDBG_TRACE_LIBS, "--- adcli output start---\n"
+ "%.*s"
+ "---adcli output end---\n",
+ (int) buf_len, buf);
+
+ close(state->read_from_child_fd);
+ state->read_from_child_fd = -1;
+
+
+ tevent_req_done(req);
+ return;
+}
+
+static void
+ad_machine_account_password_renewal_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct renewal_state *state = tevent_req_data(req, struct renewal_state);
+
+ DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for AD renewal child.\n");
+ child_handler_destroy(state->child_ctx);
+ state->child_ctx = NULL;
+ state->child_status = ETIMEDOUT;
+ tevent_req_error(req, ERR_RENEWAL_CHILD);
+}
+
+static errno_t
+ad_machine_account_password_renewal_recv(struct tevent_req *req)
+{
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+errno_t ad_machine_account_password_renewal_init(struct be_ctx *be_ctx,
+ struct ad_options *ad_opts)
+{
+ int ret;
+ struct renewal_data *renewal_data;
+ int lifetime;
+ size_t period;
+ size_t initial_delay;
+ const char *dummy;
+ char **opt_list;
+ int opt_list_size;
+ char *endptr;
+
+ lifetime = dp_opt_get_int(ad_opts->basic,
+ AD_MAXIMUM_MACHINE_ACCOUNT_PASSWORD_AGE);
+
+ if (lifetime == 0) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "Automatic machine account renewal disabled.\n");
+ return EOK;
+ }
+
+ if (lifetime < 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Illegal value [%d] for password lifetime.\n", lifetime);
+ return EINVAL;
+ }
+
+ renewal_data = talloc(be_ctx, struct renewal_data);
+ if (renewal_data == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n");
+ return ENOMEM;
+ }
+
+ dummy = dp_opt_get_cstring(ad_opts->basic,
+ AD_MACHINE_ACCOUNT_PASSWORD_RENEWAL_OPTS);
+ ret = split_on_separator(renewal_data, dummy, ':', true, false,
+ &opt_list, &opt_list_size);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed.\n");
+ goto done;
+ }
+
+ if (opt_list_size != 2) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Wrong number of renewal options.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ errno = 0;
+ period = strtouint32(opt_list[0], &endptr, 10);
+ if (errno != 0 || *endptr != '\0' || opt_list[0] == endptr) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse first renewal option.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ errno = 0;
+ initial_delay = strtouint32(opt_list[1], &endptr, 10);
+ if (errno != 0 || *endptr != '\0' || opt_list[0] == endptr) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse second renewal option.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = get_adcli_extra_args(dp_opt_get_cstring(ad_opts->basic, AD_DOMAIN),
+ dp_opt_get_cstring(ad_opts->basic, AD_HOSTNAME),
+ dp_opt_get_cstring(ad_opts->id_ctx->sdap_id_ctx->opts->basic,
+ SDAP_KRB5_KEYTAB),
+ lifetime, period, initial_delay, renewal_data);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "get_adcli_extra_args failed.\n");
+ goto done;
+ }
+
+ ret = be_ptask_create(be_ctx, be_ctx, period, initial_delay, 0, 0, 60,
+ BE_PTASK_OFFLINE_DISABLE, 0,
+ ad_machine_account_password_renewal_send,
+ ad_machine_account_password_renewal_recv,
+ renewal_data,
+ "AD machine account password renewal", NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "be_ptask_create failed.\n");
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(renewal_data);
+ }
+
+ return ret;
+}
diff --git a/src/providers/ad/ad_opts.c b/src/providers/ad/ad_opts.c
index 4ea96637ca7264c76109ed8c2f7b5e8a94f73bfe..8b2841eadc0236b51f8c9c2c02b7c98837fbe416 100644
--- a/src/providers/ad/ad_opts.c
+++ b/src/providers/ad/ad_opts.c
@@ -48,6 +48,8 @@ struct dp_option ad_basic_opts[] = {
{ "ad_gpo_default_right", DP_OPT_STRING, NULL_STRING, NULL_STRING },
{ "ad_site", DP_OPT_STRING, NULL_STRING, NULL_STRING },
{ "krb5_confd_path", DP_OPT_STRING, { KRB5_MAPPING_DIR }, NULL_STRING },
+ { "ad_maximum_machine_account_password_age", DP_OPT_NUMBER, { .number = 30 }, NULL_NUMBER },
+ { "ad_machine_account_password_renewal_opts", DP_OPT_STRING, { "86400:750" }, NULL_STRING },
DP_OPTION_TERMINATOR
};
diff --git a/src/util/util_errors.c b/src/util/util_errors.c
index ed19346d9b588a711367af4c891b1298cd4f067e..1d684d387b90b8db37609d5bc022e06fcac708f9 100644
--- a/src/util/util_errors.c
+++ b/src/util/util_errors.c
@@ -82,6 +82,7 @@ struct err_string error_to_str[] = {
{ "Address family not supported" }, /* ERR_ADDR_FAMILY_NOT_SUPPORTED */
{ "Message sender is the bus" }, /* ERR_SBUS_SENDER_BUS */
{ "Subdomain is inactive" }, /* ERR_SUBDOM_INACTIVE */
+ { "AD renewal child failed" }, /* ERR_RENEWAL_CHILD */
{ "ERR_LAST" } /* ERR_LAST */
};
diff --git a/src/util/util_errors.h b/src/util/util_errors.h
index c1d081912a382d645c27809a3ac336ff90047cdf..5c02fdd8b4c6e0c59f7fd6f66a3fc8a8e48dc607 100644
--- a/src/util/util_errors.h
+++ b/src/util/util_errors.h
@@ -104,6 +104,7 @@ enum sssd_errors {
ERR_ADDR_FAMILY_NOT_SUPPORTED,
ERR_SBUS_SENDER_BUS,
ERR_SUBDOM_INACTIVE,
+ ERR_RENEWAL_CHILD,
ERR_LAST /* ALWAYS LAST */
};
--
2.5.0