URL: https://github.com/freeipa/freeipa/pull/202
Author: martbab
 Title: #202: ipa-getkeytab enhancements
Action: synchronized

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/202/head:pr202
git checkout pr202
From 8d9bad6ac273b5598f526d1b5ffff243c9ddfc2d Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Thu, 27 Oct 2016 13:35:10 +0200
Subject: [PATCH 1/4] ipa-getkeytab: expose CA cert path as option

get rid of hardcoded CA cert path and allow the caller to use supplied custom
paths instead

https://fedorahosted.org/freeipa/ticket/6409
---
 client/ipa-getkeytab.c     | 25 +++++++++++++++++++------
 client/man/ipa-getkeytab.1 |  6 +++++-
 2 files changed, 24 insertions(+), 7 deletions(-)

diff --git a/client/ipa-getkeytab.c b/client/ipa-getkeytab.c
index 0f549a5..e32e9e4 100644
--- a/client/ipa-getkeytab.c
+++ b/client/ipa-getkeytab.c
@@ -44,6 +44,8 @@
 #include "ipa_asn1.h"
 #include "ipa-client-common.h"
 
+#define DEFAULT_CA_CERT_FILE "/etc/ipa/ca.crt"
+
 static int ldap_sasl_interact(LDAP *ld, unsigned flags, void *priv_data, void *sit)
 {
 	sasl_interact_t *in = NULL;
@@ -152,10 +154,9 @@ static int ipa_ldap_init(LDAP ** ld, const char * scheme, const char * servernam
 	return rc;
 }
 
-const char *ca_cert_file = "/etc/ipa/ca.crt";
-
 static int ipa_ldap_bind(const char *server_name, krb5_principal bind_princ,
-			 const char *bind_dn, const char *bind_pw, LDAP **_ld)
+                         const char *bind_dn, const char *bind_pw,
+                         const char *ca_cert_file, LDAP **_ld)
 {
     char *msg = NULL;
     struct berval bv;
@@ -343,6 +344,7 @@ static int ldap_set_keytab(krb5_context krbctx,
 			   krb5_principal princ,
 			   const char *binddn,
 			   const char *bindpw,
+			   const char *ca_cert_file,
 			   struct keys_container *keys)
 {
 	LDAP *ld = NULL;
@@ -369,7 +371,7 @@ static int ldap_set_keytab(krb5_context krbctx,
 		goto error_out;
 	}
 
-    ret = ipa_ldap_bind(servername, princ, binddn, bindpw, &ld);
+    ret = ipa_ldap_bind(servername, princ, binddn, bindpw, ca_cert_file, &ld);
     if (ret != LDAP_SUCCESS) {
         fprintf(stderr, _("Failed to bind to server!\n"));
         goto error_out;
@@ -500,6 +502,7 @@ static int ldap_get_keytab(krb5_context krbctx, bool generate, char *password,
                            const char *enctypes, const char *bind_server,
                            const char *svc_princ, krb5_principal bind_princ,
                            const char *bind_dn, const char *bind_pw,
+                           const char *ca_cert_file,
                            struct keys_container *keys, int *kvno,
                            char **err_msg)
 {
@@ -529,7 +532,8 @@ static int ldap_get_keytab(krb5_context krbctx, bool generate, char *password,
         goto done;
     }
 
-    ret = ipa_ldap_bind(bind_server, bind_princ, bind_dn, bind_pw, &ld);
+    ret = ipa_ldap_bind(bind_server, bind_princ, bind_dn, bind_pw,
+                        ca_cert_file, &ld);
     if (ret != LDAP_SUCCESS) {
         *err_msg = _("Failed to bind to server!\n");
         goto done;
@@ -684,6 +688,7 @@ int main(int argc, const char *argv[])
 	static const char *enctypes_string = NULL;
 	static const char *binddn = NULL;
 	static const char *bindpw = NULL;
+	static const char *ca_cert_file = NULL;
 	int quiet = 0;
 	int askpass = 0;
 	int permitted_enctypes = 0;
@@ -712,6 +717,8 @@ int main(int argc, const char *argv[])
               _("LDAP DN"), _("DN to bind as if not using kerberos") },
 	    { "bindpw", 'w', POPT_ARG_STRING, &bindpw, 0,
               _("LDAP password"), _("password to use if not using kerberos") },
+	    { "cacert", 0, POPT_ARG_STRING, &ca_cert_file, 0,
+              _("Path to the IPA CA certificate"), _("IPA CA certificate")},
 	    { "retrieve", 'r', POPT_ARG_NONE, &retrieve, 0,
               _("Retrieve current keys without changing them"), NULL },
             POPT_AUTOHELP
@@ -798,6 +805,10 @@ int main(int argc, const char *argv[])
         }
     }
 
+    if (!ca_cert_file) {
+        ca_cert_file = DEFAULT_CA_CERT_FILE;
+    }
+
     if (askpass && retrieve) {
         fprintf(stderr, _("Incompatible options provided (-r and -P)\n"));
         exit(2);
@@ -853,6 +864,7 @@ int main(int argc, const char *argv[])
     kvno = -1;
     ret = ldap_get_keytab(krbctx, (retrieve == 0), password, enctypes_string,
                           server, principal, uprinc, binddn, bindpw,
+                          ca_cert_file,
                           &keys, &kvno, &err_msg);
     if (ret) {
         if (!quiet && err_msg != NULL) {
@@ -877,7 +889,8 @@ int main(int argc, const char *argv[])
             exit(8);
         }
 
-        kvno = ldap_set_keytab(krbctx, server, principal, uprinc, binddn, bindpw, &keys);
+        kvno = ldap_set_keytab(krbctx, server, principal, uprinc, binddn,
+                               bindpw, ca_cert_file, &keys);
     }
 
     if (kvno == -1) {
diff --git a/client/man/ipa-getkeytab.1 b/client/man/ipa-getkeytab.1
index 1c27072..ee8f46b 100644
--- a/client/man/ipa-getkeytab.1
+++ b/client/man/ipa-getkeytab.1
@@ -21,7 +21,7 @@
 .SH "NAME"
 ipa\-getkeytab \- Get a keytab for a Kerberos principal
 .SH "SYNOPSIS"
-ipa\-getkeytab \fB\-p\fR \fIprincipal\-name\fR \fB\-k\fR \fIkeytab\-file\fR [ \fB\-e\fR \fIencryption\-types\fR ] [ \fB\-s\fR \fIipaserver\fR ] [ \fB\-q\fR ] [ \fB\-D\fR|\fB\-\-binddn\fR \fIBINDDN\fR ] [ \fB\-w|\-\-bindpw\fR ] [ \fB\-P\fR|\fB\-\-password\fR \fIPASSWORD\fR ] [ \fB\-r\fR ]
+ipa\-getkeytab \fB\-p\fR \fIprincipal\-name\fR \fB\-k\fR \fIkeytab\-file\fR [ \fB\-e\fR \fIencryption\-types\fR ] [ \fB\-s\fR \fIipaserver\fR ] [ \fB\-q\fR ] [ \fB\-D\fR|\fB\-\-binddn\fR \fIBINDDN\fR ] [ \fB\-w|\-\-bindpw\fR ] [ \fB\-P\fR|\fB\-\-password\fR \fIPASSWORD\fR ] [ \fB\-\-cacert \fICACERT\fR ] [ \fB\-r\fR ]
 
 .SH "DESCRIPTION"
 Retrieves a Kerberos \fIkeytab\fR.
@@ -98,6 +98,10 @@ The LDAP DN to bind as when retrieving a keytab without Kerberos credentials. Ge
 \fB\-w, \-\-bindpw\fR
 The LDAP password to use when not binding with Kerberos.
 .TP
+\fB\-\-cacert\fR
+The path to IPA CA certificate used to validate LDAPS connection. Defaults to
+/etc/ipa/ca.crt
+.TP
 \fB\-r\fR
 Retrieve mode. Retrieve an existing key from the server instead of generating a
 new one. This is incompatibile with the \-\-password option, and will work only

From b1d3400698c317f046e8e1119f6ebceaeff5126e Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Thu, 27 Oct 2016 19:06:09 +0200
Subject: [PATCH 2/4] extend ipa-getkeytab to support other LDAP bind methods

ipa-getkeytab command was augmented in a way that allows more flexible
selection of bind mechanisms:

   * -H <LDAP_URI> option was added to specify full LDAP uri. By default the
     URI will be constructed from retrieved server name as is done now.
     Specifying this options precludes use of -s.

   * -Y <EXTERNAL|GSSAPI> specifes SASL bind mechanism if no bind DN
     was given (which implies simple bind)

This allows the command to be used also locally via LDAPI, eliminating the
need to provide any credentials at all as root (e.g. in installers)

https://fedorahosted.org/freeipa/ticket/6409
---
 client/ipa-getkeytab.c     | 204 +++++++++++++++++++++++++++++++++------------
 client/man/ipa-getkeytab.1 |  16 ++--
 2 files changed, 163 insertions(+), 57 deletions(-)

diff --git a/client/ipa-getkeytab.c b/client/ipa-getkeytab.c
index e32e9e4..d46f4c1 100644
--- a/client/ipa-getkeytab.c
+++ b/client/ipa-getkeytab.c
@@ -46,6 +46,27 @@
 
 #define DEFAULT_CA_CERT_FILE "/etc/ipa/ca.crt"
 
+#define LDAP_SASL_EXTERNAL "EXTERNAL"
+#define LDAP_SASL_GSSAPI "GSSAPI"
+
+static int check_sasl_mech(const char *mech)
+{
+    int i;
+    int ret = 1;
+    const char *supported_sasl_mechs[] = {
+        LDAP_SASL_EXTERNAL,
+        LDAP_SASL_GSSAPI,
+        NULL
+    };
+
+    for (i=0; NULL != supported_sasl_mechs[i]; i++) {
+        if (strcmp(mech, supported_sasl_mechs[i]) == 0) {
+            return 0;
+        }
+    }
+    return ret;
+}
+
 static int ldap_sasl_interact(LDAP *ld, unsigned flags, void *priv_data, void *sit)
 {
 	sasl_interact_t *in = NULL;
@@ -137,60 +158,86 @@ int filter_keys(krb5_context krbctx, struct keys_container *keys,
     return n;
 }
 
-static int ipa_ldap_init(LDAP ** ld, const char * scheme, const char * servername, const int  port)
+static int ipa_server_to_uri(const char *servername, const char *mech,
+                             char **ldap_uri)
 {
-	char* url = NULL;
-	int  url_len = snprintf(url,0,"%s://%s:%d",scheme,servername,port) +1;
+    char *url = NULL;
+    int url_len = 0;
+    int port = 389;
+    char *scheme = "ldap";
 
-	url = (char *)malloc (url_len);
-	if (!url){
-		fprintf(stderr, _("Out of memory \n"));
-		return LDAP_NO_MEMORY;
-	}
-	sprintf(url,"%s://%s:%d",scheme,servername,port);
-	int rc = ldap_initialize(ld, url);
+    url_len = asprintf(&url,"%s://%s:%d",scheme,servername,port);
 
-	free(url);
-	return rc;
+    if (url_len == -1){
+        fprintf(stderr, _("Out of memory \n"));
+        return LDAP_NO_MEMORY;
+    }
+    *ldap_uri = url;
+    return 0;
 }
 
-static int ipa_ldap_bind(const char *server_name, krb5_principal bind_princ,
-                         const char *bind_dn, const char *bind_pw,
-                         const char *ca_cert_file, LDAP **_ld)
+static int ipa_ldap_init(LDAP **ld, const char *ldap_uri)
 {
-    char *msg = NULL;
-    struct berval bv;
-    int version;
-    LDAP *ld;
-    int ssl;
-    int ret;
+    int rc = 0;
+    rc = ldap_initialize(ld, ldap_uri);
 
-    /* TODO: support referrals ? */
-    if (bind_dn) {
-        ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, ca_cert_file);
+    return rc;
+}
+
+static int ipa_tls_ssl_init(LDAP *ld, const char *ldap_uri)
+{
+    int ret = LDAP_SUCCESS;
+    int tls_hard = LDAP_OPT_X_TLS_HARD;
+    int tls_demand = LDAP_OPT_X_TLS_DEMAND;
+    char *ldap = "ldap://";;
+    char *ldaps = "ldaps://";
+
+    if (strncmp(ldap_uri, ldap, strlen(ldap)) == 0) {
+        ret = ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &tls_demand);
         if (ret != LDAP_OPT_SUCCESS) {
-            fprintf(stderr, _("Unable to set LDAP_OPT_X_TLS_CERTIFICATE\n"));
+            fprintf(stderr, _("Unable to set LDAP_OPT_X_TLS_REQUIRE_CERT\n"));
             return ret;
         }
-
-        ret = ipa_ldap_init(&ld, "ldaps", server_name, 636);
+        ret = ldap_start_tls_s(ld, NULL, NULL);
         if (ret != LDAP_SUCCESS) {
-            fprintf(stderr, _("Unable to init for ldaps(636) connection\n"));
+            fprintf(stderr, _("Unable to initialize STARTTLS session\n"));
             return ret;
         }
-
-        ssl = LDAP_OPT_X_TLS_HARD;;
-        ret = ldap_set_option(ld, LDAP_OPT_X_TLS, &ssl);
+    }
+    else if (strncmp(ldap_uri, ldaps, strlen(ldaps)) == 0) {
+        ret = ldap_set_option(ld, LDAP_OPT_X_TLS, &tls_hard);
         if (ret != LDAP_OPT_SUCCESS) {
             fprintf(stderr, _("Unable to set LDAP_OPT_X_TLS\n"));
-            goto done;
-        }
-    } else {
-        ret = ipa_ldap_init(&ld, "ldap", server_name, 389);
-        if (ret != LDAP_SUCCESS) {
-            fprintf(stderr, _("Unable to init for ldap(389) connection\n"));
             return ret;
         }
+
+    }
+    return ret;
+
+}
+
+static int ipa_ldap_bind(const char *ldap_uri, krb5_principal bind_princ,
+                         const char *bind_dn, const char *bind_pw,
+                         const char *mech, const char *ca_cert_file,
+                         LDAP **_ld)
+{
+    char *msg = NULL;
+    struct berval bv;
+    int version;
+    LDAP *ld;
+    int ret;
+
+    /* TODO: support referrals ? */
+    ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, ca_cert_file);
+    if (ret != LDAP_OPT_SUCCESS) {
+        fprintf(stderr, _("Unable to set LDAP_OPT_X_TLS_CERTIFICATE\n"));
+        return ret;
+    }
+
+    ret = ipa_ldap_init(&ld, ldap_uri);
+    if (ret != LDAP_SUCCESS) {
+        fprintf(stderr, _("Unable to init connection to %s\n"), ldap_uri);
+        return ret;
     }
 
     if (ld == NULL) {
@@ -214,6 +261,11 @@ static int ipa_ldap_bind(const char *server_name, krb5_principal bind_princ,
 	goto done;
     }
 
+    ret = ipa_tls_ssl_init(ld, ldap_uri);
+    if (ret != LDAP_OPT_SUCCESS) {
+        goto done;
+    }
+
     if (bind_dn) {
         bv.bv_val = discard_const(bind_pw);
         bv.bv_len = strlen(bind_pw);
@@ -222,12 +274,17 @@ static int ipa_ldap_bind(const char *server_name, krb5_principal bind_princ,
                                &bv, NULL, NULL, NULL);
         if (ret != LDAP_SUCCESS) {
             fprintf(stderr, _("Simple bind failed\n"));
-            goto done;
         }
     } else {
-        ret = ldap_sasl_interactive_bind_s(ld, NULL, "GSSAPI",
-                                           NULL, NULL, LDAP_SASL_QUIET,
-                                           ldap_sasl_interact, bind_princ);
+        if (strcmp(mech, LDAP_SASL_EXTERNAL) == 0) {
+            ret = ldap_sasl_bind_s(ld, NULL, LDAP_SASL_EXTERNAL,
+                               NULL, NULL, NULL, NULL);
+        } else {
+            ret = ldap_sasl_interactive_bind_s(ld, NULL, LDAP_SASL_GSSAPI,
+                                               NULL, NULL, LDAP_SASL_QUIET,
+                                               ldap_sasl_interact, bind_princ);
+        }
+
         if (ret != LDAP_SUCCESS) {
 #ifdef LDAP_OPT_DIAGNOSTIC_MESSAGE
             ldap_get_option(ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&msg);
@@ -235,8 +292,8 @@ static int ipa_ldap_bind(const char *server_name, krb5_principal bind_princ,
             fprintf(stderr, "SASL Bind failed %s (%d) %s!\n",
                             ldap_err2string(ret), ret, msg ? msg : "");
             goto done;
+            }
         }
-    }
 
     ret = LDAP_SUCCESS;
 
@@ -339,11 +396,12 @@ static BerElement *get_control_data(LDAPControl **list, const char *repoid)
 }
 
 static int ldap_set_keytab(krb5_context krbctx,
-			   const char *servername,
+			   const char *ldap_uri,
 			   const char *principal_name,
 			   krb5_principal princ,
 			   const char *binddn,
 			   const char *bindpw,
+			   const char *mech,
 			   const char *ca_cert_file,
 			   struct keys_container *keys)
 {
@@ -371,7 +429,7 @@ static int ldap_set_keytab(krb5_context krbctx,
 		goto error_out;
 	}
 
-    ret = ipa_ldap_bind(servername, princ, binddn, bindpw, ca_cert_file, &ld);
+    ret = ipa_ldap_bind(ldap_uri, princ, binddn, bindpw, mech, ca_cert_file, &ld);
     if (ret != LDAP_SUCCESS) {
         fprintf(stderr, _("Failed to bind to server!\n"));
         goto error_out;
@@ -499,9 +557,10 @@ static struct berval *create_getkeytab_control(const char *svc_princ, bool gen,
 #define GKREP_SALT_TAG (LBER_CLASS_CONTEXT | LBER_CONSTRUCTED | 1)
 
 static int ldap_get_keytab(krb5_context krbctx, bool generate, char *password,
-                           const char *enctypes, const char *bind_server,
+                           const char *enctypes, const char *ldap_uri,
                            const char *svc_princ, krb5_principal bind_princ,
                            const char *bind_dn, const char *bind_pw,
+                           const char *mech,
                            const char *ca_cert_file,
                            struct keys_container *keys, int *kvno,
                            char **err_msg)
@@ -532,7 +591,7 @@ static int ldap_get_keytab(krb5_context krbctx, bool generate, char *password,
         goto done;
     }
 
-    ret = ipa_ldap_bind(bind_server, bind_princ, bind_dn, bind_pw,
+    ret = ipa_ldap_bind(ldap_uri, bind_princ, bind_dn, bind_pw, mech,
                         ca_cert_file, &ld);
     if (ret != LDAP_SUCCESS) {
         *err_msg = _("Failed to bind to server!\n");
@@ -688,6 +747,8 @@ int main(int argc, const char *argv[])
 	static const char *enctypes_string = NULL;
 	static const char *binddn = NULL;
 	static const char *bindpw = NULL;
+	char *ldap_uri = NULL;
+	static const char *sasl_mech = NULL;
 	static const char *ca_cert_file = NULL;
 	int quiet = 0;
 	int askpass = 0;
@@ -719,6 +780,12 @@ int main(int argc, const char *argv[])
               _("LDAP password"), _("password to use if not using kerberos") },
 	    { "cacert", 0, POPT_ARG_STRING, &ca_cert_file, 0,
               _("Path to the IPA CA certificate"), _("IPA CA certificate")},
+	    { "ldapuri", 'H', POPT_ARG_STRING, &ldap_uri, 0,
+              _("LDAP uri to connect to. Mutually exclusive with --server"),
+              _("url")},
+	    { "mech", 'Y', POPT_ARG_STRING, &sasl_mech, 0,
+              _("LDAP SASL bind mechanism if no bindd/bindpw"),
+              _("GSSAPI|EXTERNAL") },
 	    { "retrieve", 'r', POPT_ARG_NONE, &retrieve, 0,
               _("Retrieve current keys without changing them"), NULL },
             POPT_AUTOHELP
@@ -790,7 +857,34 @@ int main(int argc, const char *argv[])
 		exit(10);
 	}
 
-    if (!server) {
+    if (NULL != binddn && NULL != sasl_mech) {
+        fprintf(stderr, _("Cannot specify both SASL mechanism "
+                          "and bind DN simultaneously.\n"));
+		if (!quiet)
+			poptPrintUsage(pc, stderr, 0);
+		exit(2);
+    }
+
+    if (sasl_mech && check_sasl_mech(sasl_mech)) {
+        fprintf(stderr, _("Invalid SASL bind mechanism\n"));
+        if (!quiet)
+            poptPrintUsage(pc, stderr, 0);
+        exit(2);
+    }
+
+    if (!binddn && !sasl_mech) {
+        sasl_mech = LDAP_SASL_GSSAPI;
+    }
+
+    if (server && ldap_uri) {
+        fprintf(stderr, _("Cannot specify server and LDAP uri "
+                          "simultaneously.\n"));
+		if (!quiet)
+			poptPrintUsage(pc, stderr, 0);
+		exit(2);
+    }
+
+    if (!server && !ldap_uri) {
         struct ipa_config *ipacfg = NULL;
 
         ret = read_ipa_config(&ipacfg);
@@ -804,6 +898,12 @@ int main(int argc, const char *argv[])
             exit(2);
         }
     }
+    if (server) {
+        ret = ipa_server_to_uri(server, sasl_mech, &ldap_uri);
+        if (ret) {
+            exit(ret);
+        }
+    }
 
     if (!ca_cert_file) {
         ca_cert_file = DEFAULT_CA_CERT_FILE;
@@ -837,7 +937,7 @@ int main(int argc, const char *argv[])
 		exit(4);
 	}
 
-	if (NULL == bindpw) {
+	if (NULL == bindpw && strcmp(sasl_mech, LDAP_SASL_GSSAPI) == 0) {
 		krberr = krb5_cc_default(krbctx, &ccache);
 		if (krberr) {
 			fprintf(stderr,
@@ -863,8 +963,8 @@ int main(int argc, const char *argv[])
 
     kvno = -1;
     ret = ldap_get_keytab(krbctx, (retrieve == 0), password, enctypes_string,
-                          server, principal, uprinc, binddn, bindpw,
-                          ca_cert_file,
+                          ldap_uri, principal, uprinc, binddn, bindpw,
+                          sasl_mech, ca_cert_file,
                           &keys, &kvno, &err_msg);
     if (ret) {
         if (!quiet && err_msg != NULL) {
@@ -889,8 +989,8 @@ int main(int argc, const char *argv[])
             exit(8);
         }
 
-        kvno = ldap_set_keytab(krbctx, server, principal, uprinc, binddn,
-                               bindpw, ca_cert_file, &keys);
+        kvno = ldap_set_keytab(krbctx, ldap_uri, principal, uprinc, binddn,
+                               bindpw, sasl_mech, ca_cert_file, &keys);
     }
 
     if (kvno == -1) {
diff --git a/client/man/ipa-getkeytab.1 b/client/man/ipa-getkeytab.1
index ee8f46b..44e20d9 100644
--- a/client/man/ipa-getkeytab.1
+++ b/client/man/ipa-getkeytab.1
@@ -21,7 +21,7 @@
 .SH "NAME"
 ipa\-getkeytab \- Get a keytab for a Kerberos principal
 .SH "SYNOPSIS"
-ipa\-getkeytab \fB\-p\fR \fIprincipal\-name\fR \fB\-k\fR \fIkeytab\-file\fR [ \fB\-e\fR \fIencryption\-types\fR ] [ \fB\-s\fR \fIipaserver\fR ] [ \fB\-q\fR ] [ \fB\-D\fR|\fB\-\-binddn\fR \fIBINDDN\fR ] [ \fB\-w|\-\-bindpw\fR ] [ \fB\-P\fR|\fB\-\-password\fR \fIPASSWORD\fR ] [ \fB\-\-cacert \fICACERT\fR ] [ \fB\-r\fR ]
+ipa\-getkeytab \fB\-p\fR \fIprincipal\-name\fR \fB\-k\fR \fIkeytab\-file\fR [ \fB\-e\fR \fIencryption\-types\fR ] [ \fB\-s\fR \fIipaserver\fR ] [ \fB\-q\fR ] [ \fB\-D\fR|\fB\-\-binddn\fR \fIBINDDN\fR ] [ \fB\-w|\-\-bindpw\fR ] [ \fB\-P\fR|\fB\-\-password\fR \fIPASSWORD\fR ] [ \fB\-\-cacert \fICACERT\fR ] [ \fB\-H|\-\-ldapuri \fIURI\fR ] [ \fB\-Y|\-\-mech \fIGSSAPI|EXTERNAL\fR ] [ \fB\-r\fR ]
 
 .SH "DESCRIPTION"
 Retrieves a Kerberos \fIkeytab\fR.
@@ -73,7 +73,7 @@ des\-cbc\-crc
 \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)
+(/etc/ipa/default.conf). Cannot be used together with \fB\-H\fR.
 .TP
 \fB\-q\fR
 Quiet mode. Only errors are displayed.
@@ -96,11 +96,17 @@ Use this password for the key instead of one randomly generated.
 The LDAP DN to bind as when retrieving a keytab without Kerberos credentials. Generally used with the \fB\-w\fR option.
 .TP
 \fB\-w, \-\-bindpw\fR
-The LDAP password to use when not binding with Kerberos.
+The LDAP password to use when not binding with Kerberos. \fB\-D\fR and \fB\-w\fR can not be used together with \fB\-Y\fR.
 .TP
 \fB\-\-cacert\fR
-The path to IPA CA certificate used to validate LDAPS connection. Defaults to
-/etc/ipa/ca.crt
+The path to IPA CA certificate used to validate LDAPS/STARTTLS connection.
+Defaults to /etc/ipa/ca.crt
+.TP
+\fB\-H, \-\-ldapuri\fR
+LDAP URI. If ldap:// is specified, STARTTLS session is initiated by default. Can not be used with \fB\-s\fR.
+.TP
+\fB\-Y, \-\-mech\fR
+SASL mechanism to use if fB\-D\fR and \fB\-w\fR are not used. Choose between GSSAPI and EXTERNAL.
 .TP
 \fB\-r\fR
 Retrieve mode. Retrieve an existing key from the server instead of generating a

From 58bff35b161cab65cde1fb8b3c82c5c6d42572f5 Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Mon, 31 Oct 2016 12:30:34 +0100
Subject: [PATCH 3/4] Modernize ipa-getkeytab test suite

The test suite is now leveraging host/service tracker objects as test case
fixture, removing much of ad-hoc setup/teardown.

https://fedorahosted.org/freeipa/ticket/640
---
 ipatests/test_cmdline/test_ipagetkeytab.py     | 128 +++++++++++++------------
 ipatests/test_xmlrpc/tracker/service_plugin.py |   4 +
 2 files changed, 71 insertions(+), 61 deletions(-)

diff --git a/ipatests/test_cmdline/test_ipagetkeytab.py b/ipatests/test_cmdline/test_ipagetkeytab.py
index b3c8491..1f4581a 100644
--- a/ipatests/test_cmdline/test_ipagetkeytab.py
+++ b/ipatests/test_cmdline/test_ipagetkeytab.py
@@ -28,10 +28,10 @@
 import pytest
 
 from ipalib import api
-from ipalib import errors
 from ipapython import ipautil, ipaldap
 from ipaserver.plugins.ldap2 import ldap2
 from ipatests.test_cmdline.cmdline import cmdline_test
+from ipatests.test_xmlrpc.tracker import host_plugin, service_plugin
 
 def use_keytab(principal, keytab):
     try:
@@ -53,104 +53,110 @@ def use_keytab(principal, keytab):
             shutil.rmtree(tmpdir)
 
 
+@pytest.fixture(scope='class')
+def test_host(request):
+    host_tracker = host_plugin.HostTracker(u'test-host')
+    return host_tracker.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def test_service(request, test_host):
+    service_tracker = service_plugin.ServiceTracker(u'srv', test_host.name)
+    test_host.ensure_exists()
+    return service_tracker.make_fixture(request)
+
+
 @pytest.mark.tier0
 class test_ipagetkeytab(cmdline_test):
     """
     Test `ipa-getkeytab`.
     """
     command = "ipa-getkeytab"
-    host_fqdn = u'ipatest.%s' % api.env.domain
-    service_princ = u'test/%s@%s' % (host_fqdn, api.env.realm)
-    [keytabfd, keytabname] = tempfile.mkstemp()
-    os.close(keytabfd)
+    keytabname = None
+
+    @classmethod
+    def setup_class(cls):
+        super(test_ipagetkeytab, cls).setup_class()
+
+        keytabfd, keytabname = tempfile.mkstemp()
+
+        os.close(keytabfd)
+        os.unlink(keytabname)
+
+        cls.keytabname = keytabname
+
+    @classmethod
+    def teardown_class(cls):
+        super(test_ipagetkeytab, cls).teardown_class()
 
-    def test_0_setup(self):
-        """
-        Create a host to test against.
-        """
-        # Create the service
         try:
-            api.Command['host_add'](self.host_fqdn, force=True)
-        except errors.DuplicateEntry:
-            # it already exists, no problem
+            os.unlink(cls.keytabname)
+        except OSError:
             pass
 
-    def test_1_run(self):
+    def run_ipagetkeytab(self, service_principal, raiseonerr=False):
+        new_args = [self.command,
+                    "-s", api.env.host,
+                    "-p", service_principal,
+                    "-k", self.keytabname]
+        return ipautil.run(
+            new_args,
+            stdin=None,
+            raiseonerr=raiseonerr,
+            capture_error=True)
+
+    def test_1_run(self, test_service):
         """
         Create a keytab with `ipa-getkeytab` for a non-existent service.
         """
-        new_args = [self.command,
-                    "-s", api.env.host,
-                    "-p", "test/notfound.example.com",
-                    "-k", self.keytabname,
-                   ]
-        result = ipautil.run(new_args, stdin=None, raiseonerr=False,
-                             capture_error=True)
+        test_service.ensure_missing()
+        result = self.run_ipagetkeytab(test_service.name)
         err = result.error_output
+
         assert 'Failed to parse result: PrincipalName not found.\n' in err, err
         rc = result.returncode
         assert rc > 0, rc
 
-    def test_2_run(self):
+    def test_2_run(self, test_service):
         """
         Create a keytab with `ipa-getkeytab` for an existing service.
         """
-        # Create the service
-        try:
-            api.Command['service_add'](self.service_princ, force=True)
-        except errors.DuplicateEntry:
-            # it already exists, no problem
-            pass
+        test_service.ensure_exists()
 
-        os.unlink(self.keytabname)
-        new_args = [self.command,
-                    "-s", api.env.host,
-                    "-p", self.service_princ,
-                    "-k", self.keytabname,
-                   ]
-        try:
-            result = ipautil.run(new_args, None, capture_error=True)
-            expected = 'Keytab successfully retrieved and stored in: %s\n' % (
-                self.keytabname)
-            assert expected in result.error_output, (
-                'Success message not in output:\n%s' % result.error_output)
-        except ipautil.CalledProcessError:
-            assert (False)
-
-    def test_3_use(self):
+        result = self.run_ipagetkeytab(test_service.name, raiseonerr=True)
+        expected = 'Keytab successfully retrieved and stored in: %s\n' % (
+            self.keytabname)
+        assert expected in result.error_output, (
+            'Success message not in output:\n%s' % result.error_output)
+
+    def test_3_use(self, test_service):
         """
         Try to use the service keytab.
         """
-        use_keytab(self.service_princ, self.keytabname)
+        use_keytab(test_service.name, self.keytabname)
 
-    def test_4_disable(self):
+    def test_4_disable(self, test_service):
         """
         Disable a kerberos principal
         """
+        retrieve_cmd = test_service.make_retrieve_command()
+        result = retrieve_cmd()
         # Verify that it has a principal key
-        entry = api.Command['service_show'](self.service_princ)['result']
-        assert(entry['has_keytab'] == True)
+        assert result[u'result'][u'has_keytab']
 
         # Disable it
-        api.Command['service_disable'](self.service_princ)
+        disable_cmd = test_service.make_disable_command()
+        disable_cmd()
 
         # Verify that it looks disabled
-        entry = api.Command['service_show'](self.service_princ)['result']
-        assert(entry['has_keytab'] == False)
+        result = retrieve_cmd()
+        assert not result[u'result'][u'has_keytab']
 
-    def test_5_use_disabled(self):
+    def test_5_use_disabled(self, test_service):
         """
         Try to use the disabled keytab
         """
         try:
-            use_keytab(self.service_princ, self.keytabname)
+            use_keytab(test_service.name, self.keytabname)
         except Exception as errmsg:
             assert('Unable to bind to LDAP. Error initializing principal' in str(errmsg))
-
-    def test_9_cleanup(self):
-        """
-        Clean up test data
-        """
-        # First create the host that will use this policy
-        os.unlink(self.keytabname)
-        api.Command['host_del'](self.host_fqdn)
diff --git a/ipatests/test_xmlrpc/tracker/service_plugin.py b/ipatests/test_xmlrpc/tracker/service_plugin.py
index 0a90115..7e51aca 100644
--- a/ipatests/test_xmlrpc/tracker/service_plugin.py
+++ b/ipatests/test_xmlrpc/tracker/service_plugin.py
@@ -85,6 +85,10 @@ def make_update_command(self, updates):
 
         return self.make_command('service_mod', self.name, **updates)
 
+    def make_disable_command(self):
+        """ make command  that disables the service principal """
+        return self.make_command('service_disable', self.name)
+
     def create(self, force=True):
         """Helper function to create an entry and check the result"""
         self.ensure_missing()

From 35e2e2ae82d66ef369ac29a36705751963cd2735 Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Mon, 31 Oct 2016 13:58:47 +0100
Subject: [PATCH 4/4] Extend keytab retrieval test suite to cover new options

All new retrieval methods are covered including testing for excluded option
combinations.

https://fedorahosted.org/freeipa/ticket/6409
---
 ipatests/test_cmdline/test_ipagetkeytab.py | 185 +++++++++++++++++++++++++++--
 1 file changed, 173 insertions(+), 12 deletions(-)

diff --git a/ipatests/test_cmdline/test_ipagetkeytab.py b/ipatests/test_cmdline/test_ipagetkeytab.py
index 1f4581a..25c31ea 100644
--- a/ipatests/test_cmdline/test_ipagetkeytab.py
+++ b/ipatests/test_cmdline/test_ipagetkeytab.py
@@ -66,17 +66,16 @@ def test_service(request, test_host):
     return service_tracker.make_fixture(request)
 
 
-@pytest.mark.tier0
-class test_ipagetkeytab(cmdline_test):
+class KeytabRetrievalTest(cmdline_test):
     """
-    Test `ipa-getkeytab`.
+    Base class for keytab retrieval tests
     """
     command = "ipa-getkeytab"
     keytabname = None
 
     @classmethod
     def setup_class(cls):
-        super(test_ipagetkeytab, cls).setup_class()
+        super(KeytabRetrievalTest, cls).setup_class()
 
         keytabfd, keytabname = tempfile.mkstemp()
 
@@ -87,24 +86,54 @@ def setup_class(cls):
 
     @classmethod
     def teardown_class(cls):
-        super(test_ipagetkeytab, cls).teardown_class()
+        super(KeytabRetrievalTest, cls).teardown_class()
 
         try:
             os.unlink(cls.keytabname)
         except OSError:
             pass
 
-    def run_ipagetkeytab(self, service_principal, raiseonerr=False):
+    def run_ipagetkeytab(self, service_principal, args=tuple(),
+                         raiseonerr=False):
         new_args = [self.command,
-                    "-s", api.env.host,
                     "-p", service_principal,
                     "-k", self.keytabname]
+
+        if not args:
+            new_args.extend(['-s', api.env.host])
+        else:
+            new_args.extend(list(args))
+
         return ipautil.run(
             new_args,
             stdin=None,
             raiseonerr=raiseonerr,
             capture_error=True)
 
+    def assert_success(self, *args, **kwargs):
+        result = self.run_ipagetkeytab(*args, **kwargs)
+        expected = 'Keytab successfully retrieved and stored in: %s\n' % (
+            self.keytabname)
+        assert expected in result.error_output, (
+            'Success message not in output:\n%s' % result.error_output)
+
+    def assert_failure(self, retcode, message, *args, **kwargs):
+        result = self.run_ipagetkeytab(*args, **kwargs)
+        err = result.error_output
+
+        assert message in err
+        rc = result.returncode
+        assert rc == retcode
+
+
+@pytest.mark.tier0
+class test_ipagetkeytab(KeytabRetrievalTest):
+    """
+    Test `ipa-getkeytab`.
+    """
+    command = "ipa-getkeytab"
+    keytabname = None
+
     def test_1_run(self, test_service):
         """
         Create a keytab with `ipa-getkeytab` for a non-existent service.
@@ -123,11 +152,7 @@ def test_2_run(self, test_service):
         """
         test_service.ensure_exists()
 
-        result = self.run_ipagetkeytab(test_service.name, raiseonerr=True)
-        expected = 'Keytab successfully retrieved and stored in: %s\n' % (
-            self.keytabname)
-        assert expected in result.error_output, (
-            'Success message not in output:\n%s' % result.error_output)
+        self.assert_success(test_service.name, raiseonerr=True)
 
     def test_3_use(self, test_service):
         """
@@ -160,3 +185,139 @@ def test_5_use_disabled(self, test_service):
             use_keytab(test_service.name, self.keytabname)
         except Exception as errmsg:
             assert('Unable to bind to LDAP. Error initializing principal' in str(errmsg))
+
+
+class TestBindMethods(KeytabRetrievalTest):
+    """
+    Class that tests '-c'/'-H'/'-Y' flags
+    """
+
+    dm_password = None
+    ca_cert = None
+
+    @classmethod
+    def setup_class(cls):
+        super(TestBindMethods, cls).setup_class()
+
+        dmpw_file = os.path.join(api.env.dot_ipa, '.dmpw')
+
+        if not os.path.isfile(dmpw_file):
+            pytest.skip('{} file required for this test'.format(dmpw_file))
+
+        with open(dmpw_file, 'r') as f:
+            cls.dm_password = f.read().strip()
+
+        tempfd, temp_ca_cert = tempfile.mkstemp()
+
+        os.close(tempfd)
+
+        shutil.copy(os.path.join(api.env.confdir, 'ca.crt'), temp_ca_cert)
+
+        cls.ca_cert = temp_ca_cert
+
+    @classmethod
+    def teardown_class(cls):
+        super(TestBindMethods, cls).teardown_class()
+
+        try:
+            os.unlink(cls.ca_cert)
+        except OSError:
+            pass
+
+    def check_ldapi(self):
+        if not api.env.ldap_uri.startswith('ldapi://'):
+            pytest.skip("LDAP URI not pointing to LDAPI socket")
+
+    def test_retrieval_with_dm_creds(self, test_service):
+        test_service.ensure_exists()
+
+        self.assert_success(
+            test_service.name,
+            args=[
+                '-D', "cn=Directory Manager",
+                '-w', self.dm_password,
+                '-s', api.env.host])
+
+    def test_retrieval_using_plain_ldap(self, test_service):
+        test_service.ensure_exists()
+        ldap_uri = 'ldap://{}'.format(api.env.host)
+
+        self.assert_success(
+            test_service.name,
+            args=[
+                '-D', "cn=Directory Manager",
+                '-w', self.dm_password,
+                '-H', ldap_uri])
+
+    @pytest.mark.skipif(os.geteuid() != 0,
+                        reason="Must have root privileges to run this test")
+    def test_retrieval_using_ldapi_external(self, test_service):
+        test_service.ensure_exists()
+        self.check_ldapi()
+
+        self.assert_success(
+            test_service.name,
+            args=[
+                '-Y',
+                'EXTERNAL',
+                '-H', api.env.ldap_uri])
+
+    def test_retrieval_using_ldap_gssapi(self, test_service):
+        test_service.ensure_exists()
+        self.check_ldapi()
+
+        self.assert_success(
+            test_service.name,
+            args=[
+                '-Y',
+                'GSSAPI',
+                '-H', api.env.ldap_uri])
+
+    def test_retrieval_using_ldaps_ca_cert(self, test_service):
+        test_service.ensure_exists()
+
+        self.assert_success(
+            test_service.name,
+            args=[
+                '-D', "cn=Directory Manager",
+                '-w', self.dm_password,
+                '-H', 'ldaps://{}'.format(api.env.host),
+                '--cacert', self.ca_cert])
+
+    def test_ldap_uri_server_raises_error(self, test_service):
+        test_service.ensure_exists()
+
+        self.assert_failure(
+            2,
+            "Cannot specify server and LDAP uri simultaneously",
+            test_service.name,
+            args=[
+                '-H', 'ldaps://{}'.format(api.env.host),
+                '-s', api.env.host],
+            raiseonerr=False)
+
+    def test_invalid_mech_raises_error(self, test_service):
+        test_service.ensure_exists()
+
+        self.assert_failure(
+            2,
+            "Invalid SASL bind mechanism",
+            test_service.name,
+            args=[
+                '-H', 'ldaps://{}'.format(api.env.host),
+                '-Y', 'BOGUS'],
+            raiseonerr=False)
+
+    def test_mech_bind_dn_raises_error(self, test_service):
+        test_service.ensure_exists()
+
+        self.assert_failure(
+            2,
+            "Cannot specify both SASL mechanism and bind DN simultaneously",
+            test_service.name,
+            args=[
+                '-D', "cn=Directory Manager",
+                '-w', self.dm_password,
+                '-H', 'ldaps://{}'.format(api.env.host),
+                '-Y', 'EXTERNAL'],
+            raiseonerr=False)
-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to