URL: https://github.com/freeipa/freeipa/pull/5934 Author: rcritten Title: #5934: ipa-getkeytab: add option to discover servers using DNS SRV Action: opened
PR body: """ Manual backport. The test did not merge cleanly. The basic flow is: If server is provided by the user then use it If server the magic value '_srv', check for _ldap._tcp SRV records for the domain in /etc/ipa/default.conf If no servers are found use the server from default.conf https://pagure.io/freeipa/issue/8478 Signed-off-by: Rob Crittenden rcrit...@redhat.com """ To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/5934/head:pr5934 git checkout pr5934
From 7fb8320f4b2e2bf5587687c96a61d521648a934f Mon Sep 17 00:00:00 2001 From: Rob Crittenden <rcrit...@redhat.com> Date: Thu, 15 Jul 2021 15:11:28 -0400 Subject: [PATCH 1/3] ipa-getkeytab: add option to discover servers using DNS SRV The basic flow is: - If server is provided by the user then use it - If server the magic value '_srv', check for _ldap._tcp SRV records for the domain in /etc/ipa/default.conf - If no servers are found use the server from default.conf https://pagure.io/freeipa/issue/8478 Signed-off-by: Rob Crittenden <rcrit...@redhat.com> Reviewed-By: Alexander Bokovoy <aboko...@redhat.com> --- client/Makefile.am | 1 + client/ipa-getkeytab.c | 221 +++++++++++++++++++++++++++++++++++++ client/man/ipa-getkeytab.1 | 5 +- configure.ac | 10 ++ 4 files changed, 236 insertions(+), 1 deletion(-) diff --git a/client/Makefile.am b/client/Makefile.am index 0031c04a558..72f4cb3dc75 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -66,6 +66,7 @@ ipa_getkeytab_LDADD = \ $(SASL_LIBS) \ $(POPT_LIBS) \ $(LIBINTL_LIBS) \ + $(RESOLV_LIBS) \ $(INI_LIBS) \ $(NULL) diff --git a/client/ipa-getkeytab.c b/client/ipa-getkeytab.c index 04786be9ee1..d3673eb058f 100644 --- a/client/ipa-getkeytab.c +++ b/client/ipa-getkeytab.c @@ -34,9 +34,11 @@ #include <time.h> #include <krb5.h> #include <ldap.h> +#include <resolv.h> #include <sasl/sasl.h> #include <popt.h> #include <ini_configobj.h> +#include <openssl/rand.h> #include "config.h" @@ -46,6 +48,174 @@ #include "ipa_ldap.h" +struct srvrec { + char *host; + uint16_t port; + int priority, weight; + struct srvrec *next; +}; + +static int +srvrec_priority_sort(const void *a, const void *b) +{ + const struct srvrec *sa, *sb; + + sa = a; + sb = b; + return sa->priority - sb->priority; +} + +static int +srvrec_sort_weight(const void *a, const void *b) +{ + const struct srvrec *sa, *sb; + + sa = a; + sb = b; + return sa->weight - sb->weight; +} + +/* Return a uniform random number between 0 and range */ +static double +rand_inclusive(double range) +{ + long long r; + + if (range == 0) { + return 0; + } + + if (RAND_bytes((unsigned char *) &r, sizeof(r)) == -1) { + return 0; + } + if (r < 0) { + r = -r; + } + return ((double)r / (double)LLONG_MAX) * range; +} + +static void +sort_prio_weight(struct srvrec *res, int len) +{ + int i, j; + double tweight; + struct srvrec tmp; + double r; + + qsort(res, len, sizeof(res[0]), srvrec_sort_weight); + for (i = 0; i < len - 1; i++) { + tweight = 0; + for (j = i; j < len; j++) { + /* Give records with 0 weight a small chance */ + tweight += res[j].weight ? res[j].weight : 0.01; + } + r = rand_inclusive(tweight); + tweight = 0; + for (j = i; j < len; j++) { + tweight += res[j].weight ? res[j].weight : 0.01; + if (tweight >= r) { + break; + } + } + if (j >= len) { + continue; + } + memcpy(&tmp, &res[i], sizeof(tmp)); + memcpy(&res[i], &res[j], sizeof(tmp)); + memcpy(&res[j], &tmp, sizeof(tmp)); + } +} + +/* The caller is responsible for freeing the results */ +static int +query_srv(const char *name, const char *domain, struct srvrec **results) +{ + int i, j, len; + unsigned char *answer = NULL; + size_t answer_len = NS_MAXMSG; + struct srvrec *res = NULL; + ns_msg msg; + ns_rr rr; + int rv = -1; + + *results = NULL; + if ((name == NULL) || (strlen(name) == 0) || + (domain == NULL) || (strlen(domain) == 0)) { + return -1; + } + + res_init(); + answer = malloc(answer_len + 1); + if (answer == NULL) { + return -1; + } + memset(answer, 0, answer_len + 1); + i = res_querydomain(name, domain, C_IN, T_SRV, answer, answer_len); + if (i == -1) { + goto error; + } + answer_len = i; + memset(&msg, 0, sizeof(msg)); + if (ns_initparse(answer, answer_len, &msg) != 0) { + goto error; + } + memset(&rr, 0, sizeof(rr)); + for (i = 0; ns_parserr(&msg, ns_s_an, i, &rr) == 0; i++) { + continue; + } + if (i == 0) { + goto error; + } + len = i; + res = malloc(sizeof(*res) * i); + if (res == NULL) { + goto error; + } + memset(res, 0, sizeof(*res) * i); + for (i = 0, j = 0; i < len; i++) { + if (ns_parserr(&msg, ns_s_an, i, &rr) != 0) { + continue; + } + if (rr.rdlength < 6) { + continue; + } + res[j].host = malloc(rr.rdlength - 6 + 1); + if (res[j].host == NULL) { + goto error; + } + res[j].priority = ntohs(*(uint16_t *)rr.rdata); + res[j].weight = ntohs(*(uint16_t *)(rr.rdata + 2)); + res[j].port = ntohs(*(uint16_t *)(rr.rdata + 4)); + memcpy(res[j].host, rr.rdata + 6, rr.rdlength - 6); + if (ns_name_ntop(rr.rdata + 6, res[j].host, rr.rdlength - 6) == -1) { + continue; + } + res[j].host[rr.rdlength - 6] = '\0'; + j++; + } + len = j; + qsort(res, len, sizeof(res[0]), srvrec_priority_sort); + i = 0; + while (i < len) { + j = i + 1; + while (j < len && (res[j].priority == res[i].priority)) { + j++; + } + sort_prio_weight(res + i, j - i); + i = j; + } + /* Fixup the linked-list pointers */ + for (i = 0; i < len - 1; i++) { + res[i].next = &res[i + 1]; + } + *results = res; + rv = 0; + +error: + free(answer); + return rv; +} + static int check_sasl_mech(const char *mech) { int i; @@ -619,6 +789,7 @@ static char *ask_password(krb5_context krbctx, char *prompt1, char *prompt2, struct ipa_config { const char *server_name; + const char *domain; }; static int config_from_file(struct ini_cfgobj *cfgctx) @@ -688,6 +859,11 @@ int read_ipa_config(struct ipa_config **ipacfg) if (ret == 0 && obj != NULL) { (*ipacfg)->server_name = ini_get_string_config_value(obj, &ret); } + ret = ini_get_config_valueobj("global", "domain", cfgctx, + INI_GET_LAST_VALUE, &obj); + if (ret == 0 && obj != NULL) { + (*ipacfg)->domain = ini_get_string_config_value(obj, &ret); + } return 0; } @@ -754,6 +930,7 @@ int main(int argc, const char *argv[]) static const char *sasl_mech = NULL; static const char *ca_cert_file = NULL; int quiet = 0; + int verbose = 0; int askpass = 0; int askbindpw = 0; int permitted_enctypes = 0; @@ -761,6 +938,8 @@ int main(int argc, const char *argv[]) struct poptOption options[] = { { "quiet", 'q', POPT_ARG_NONE, &quiet, 0, _("Print as little as possible"), _("Output only on errors")}, + { "verbose", 'v', POPT_ARG_NONE, &verbose, 0, + _("Print debugging information"), _("Output debug info")}, { "server", 's', POPT_ARG_STRING, &server, 0, _("Contact this specific KDC Server"), _("Server Name") }, @@ -906,6 +1085,41 @@ int main(int argc, const char *argv[]) exit(2); } + if (server && (strcasecmp(server, "_srv_") == 0)) { + struct srvrec *srvrecs, *srv; + struct ipa_config *ipacfg = NULL; + + ret = read_ipa_config(&ipacfg); + if (ret == 0 && ipacfg->domain && verbose) { + fprintf(stderr, _("DNS discovery for domain %s\n"), ipacfg->domain); + } + if (query_srv("_ldap._tcp", ipacfg->domain, &srvrecs) == 0) { + for (srv = srvrecs; (srv != NULL); srv = srv->next) { + if (verbose) { + fprintf(stderr, _("Discovered server %s\n"), srv->host); + } + } + for (srv = srvrecs; (srv != NULL); srv = srv->next) { + server = strdup(srv->host); + if (verbose) { + fprintf(stderr, _("Using discovered server %s\n"), server); + } + break; + } + for (srv = srvrecs; (srv != NULL); srv = srv->next) { + free(srv->host); + } + } else { + if (verbose) { + fprintf(stderr, _("DNS Discovery failed\n")); + } + } + if (strcasecmp(server, "_srv_") == 0) { + /* Discovery failed, fall through to option methods */ + server = NULL; + } + } + if (!server && !ldap_uri) { struct ipa_config *ipacfg = NULL; @@ -915,10 +1129,17 @@ int main(int argc, const char *argv[]) ipacfg->server_name = NULL; } free(ipacfg); + if (verbose && server) { + fprintf(stderr, _("Using server from config %s\n"), server); + } if (!server) { fprintf(stderr, _("Server name not provided and unavailable\n")); exit(2); } + } else { + if (verbose) { + fprintf(stderr, _("Using provided server %s\n"), server); + } } if (server) { ret = ipa_server_to_uri(server, sasl_mech, &ldap_uri); diff --git a/client/man/ipa-getkeytab.1 b/client/man/ipa-getkeytab.1 index b57c5489c08..07d2d73b327 100644 --- a/client/man/ipa-getkeytab.1 +++ b/client/man/ipa-getkeytab.1 @@ -78,7 +78,10 @@ arcfour\-hmac \fB\-s ipaserver\fR The IPA server to retrieve the keytab from (FQDN). If this option is not provided the server name is read from the IPA configuration file -(/etc/ipa/default.conf). Cannot be used together with \fB\-H\fR. +(/etc/ipa/default.conf). Cannot be used together with \fB\-H\fR. If the +value is _srv_ then DNS discovery will be used to determine a server. +If this discovery fails then it will fall back to using the configuration +file. .TP \fB\-q\fR Quiet mode. Only errors are displayed. diff --git a/configure.ac b/configure.ac index dc79d5dce70..9d7a3382597 100644 --- a/configure.ac +++ b/configure.ac @@ -108,6 +108,16 @@ LDAP_CFLAGS="" AC_SUBST(LDAP_LIBS) AC_SUBST(LDAP_CFLAGS) +dnl --------------------------------------------------------------------------- +dnl - Check for resolv library +dnl --------------------------------------------------------------------------- + +SAVE_CPPFLAGS=$CPPFLAGS +CPPFLAGS="$NSPR_CFLAGS $NSS_CFLAGS" +AC_CHECK_LIB(resolv,main,RESOLV_LIBS=-lresolv) +AC_CHECK_HEADERS(resolv.h) +AC_SUBST(RESOLV_LIBS) + dnl --------------------------------------------------------------------------- dnl - Check for OpenSSL Crypto library dnl --------------------------------------------------------------------------- From d42b58aa7089bcecd80e42df0dc161cb59a693cc Mon Sep 17 00:00:00 2001 From: Rob Crittenden <rcrit...@redhat.com> Date: Thu, 15 Jul 2021 17:52:54 -0400 Subject: [PATCH 2/3] ipa-getkeytab: fix compiler warnings Make read_ipa_config and filter_keys static to avoid "no previous prototype" warnings. Use correct datatype of return value for ber_scanf to correct different signedness comparision. Fixed while working on https://pagure.io/freeipa/issue/8478 Signed-off-by: Rob Crittenden <rcrit...@redhat.com> Reviewed-By: Alexander Bokovoy <aboko...@redhat.com> --- client/ipa-getkeytab.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ipa-getkeytab.c b/client/ipa-getkeytab.c index d3673eb058f..309b3c70432 100644 --- a/client/ipa-getkeytab.c +++ b/client/ipa-getkeytab.c @@ -291,7 +291,7 @@ static int ldap_sasl_interact(LDAP *ld, unsigned flags, void *priv_data, void *s return ret; } -int filter_keys(krb5_context krbctx, struct keys_container *keys, +static int filter_keys(krb5_context krbctx, struct keys_container *keys, ber_int_t *enctypes) { struct krb_key_salt *ksdata; @@ -507,7 +507,7 @@ static int ldap_set_keytab(krb5_context krbctx, BerElement *sctrl = NULL; struct berval *control = NULL; LDAPControl **srvctrl = NULL; - int ret; + ber_tag_t ret; int kvno, i; ber_tag_t rtag; ber_int_t *encs = NULL; @@ -826,7 +826,7 @@ static int config_from_file(struct ini_cfgobj *cfgctx) return 0; } -int read_ipa_config(struct ipa_config **ipacfg) +static int read_ipa_config(struct ipa_config **ipacfg) { struct ini_cfgobj *cfgctx = NULL; struct value_obj *obj = NULL; From ffbd9a1275d5e7c11321ea6e723608094ce1b80a Mon Sep 17 00:00:00 2001 From: Rob Crittenden <rcrit...@redhat.com> Date: Fri, 16 Jul 2021 12:59:47 -0400 Subject: [PATCH 3/3] ipatests: test ipa-getkeytab server option Test various usages of the -s/--server option: * -s is defined, use it as the server * no -s, use the host value from /etc/ipa/default.conf * -s is '_srv_', do DNS discovery https://pagure.io/freeipa/issue/8478 Signed-off-by: Rob Crittenden <rcrit...@redhat.com> Reviewed-By: Alexander Bokovoy <aboko...@redhat.com> --- ipatests/test_integration/test_commands.py | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py index d64519eb75d..2035ced562f 100644 --- a/ipatests/test_integration/test_commands.py +++ b/ipatests/test_integration/test_commands.py @@ -1467,6 +1467,64 @@ def test_proxycommand_invalid_shell(self): assert 'This account is currently not available' in \ result.stdout_text + def test_ipa_getkeytab_server(self): + """ + Exercise the ipa-getkeytab server options + + This relies on the behavior that without a TGT + ipa-getkeytab will quit and not do much of anything. + + A bogus keytab and principal are passed in to satisfy the + minimum requirements. + """ + tasks.kdestroy_all(self.master) + + # Pass in a server name to use + result = self.master.run_command( + [ + paths.IPA_GETKEYTAB, + "-k", + "/tmp/keytab", + "-p", + "foo", + "-s", + self.master.hostname, + "-v", + ], raiseonerr=False).stderr_text + + assert 'Using provided server %s' % self.master.hostname in result + + # Don't pass in a name, should use /etc/ipa/default.conf + result = self.master.run_command( + [ + paths.IPA_GETKEYTAB, + "-k", + "/tmp/keytab", + "-p", + "foo", + "-v", + ], raiseonerr=False).stderr_text + + assert ( + 'Using server from config %s' % self.master.hostname + in result + ) + + # Use DNS SRV lookup + result = self.master.run_command( + [ + paths.IPA_GETKEYTAB, + "-k", + "/tmp/keytab", + "-p", + "foo", + "-s", + "_srv_", + "-v", + ], raiseonerr=False).stderr_text + + assert 'Discovered server %s' % self.master.hostname in result + class TestIPACommandWithoutReplica(IntegrationTest): """
_______________________________________________ FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org To unsubscribe send an email to freeipa-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/freeipa-devel@lists.fedorahosted.org Do not reply to spam on the list, report it: https://pagure.io/fedora-infrastructure