From 05877c9d942b6d25b2024ce2a99e906a91b2321d Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Tue, 12 Jan 2016 11:05:02 +0100 Subject: [PATCH 3/6] 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 --- 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 a9d3f25d3775f6ac824b9f9b85dd0412417c33d3..8f72c4b440d0745becd1675c3cc23fdb003645bf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3028,6 +3028,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 fe2971d993ccf2975bbddbe6c74572ab9f5d4e51..647d081255d738c028f73bc5d65eff58cebdbdf1 100644 --- a/src/config/SSSDConfig/__init__.py.in +++ b/src/config/SSSDConfig/__init__.py.in @@ -200,6 +200,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 725663b7d1e730fbec8cbabc1ad5526a7bb1aac5..05520d14d0b6d7b61210e6a67d7f1b88ee492ad0 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 + ad_maximum_machine_account_password_age (integer) + + + 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. + + + Default: 30 days + + + + + + ad_machine_account_password_renewal_opts (string) + + + 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. + + + Default: 86400:750 (24h and 15m) + + + + + dyndns_update (boolean) diff --git a/src/providers/ad/ad_common.h b/src/providers/ad/ad_common.h index 49e97b0bebf18e795be2549c22de74111ae06583..d61be42ccfa5d61c0521ef8b3589c72a0d51c05f 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 */ }; @@ -179,4 +181,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 + + 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 . +*/ + + +#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 e7f30ab3c982ebca32844ec3755a69bc00f85f4f..59ae63ab8d6e834a772349b162bf282f9a4f1c72 100644 --- a/src/util/util_errors.c +++ b/src/util/util_errors.c @@ -83,6 +83,7 @@ struct err_string error_to_str[] = { { "Message sender is the bus" }, /* ERR_SBUS_SENDER_BUS */ { "Subdomain is inactive" }, /* ERR_SUBDOM_INACTIVE */ { "Account is locked" }, /* ERR_ACCOUNT_LOCKED */ + { "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 a1c822c4b03817cb8ebf32a9adeb657de57c9de6..05791f2f08f107a8b4830b810b8826983763174f 100644 --- a/src/util/util_errors.h +++ b/src/util/util_errors.h @@ -105,6 +105,7 @@ enum sssd_errors { ERR_SBUS_SENDER_BUS, ERR_SUBDOM_INACTIVE, ERR_ACCOUNT_LOCKED, + ERR_RENEWAL_CHILD, ERR_LAST /* ALWAYS LAST */ }; -- 2.1.0