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

PR body:
"""
This PR implements '-H' and '-Y' options mentioned in
https://fedorahosted.org/freeipa/ticket/6409 along with the ability to specify
CA cert on the command line (which proved useful during the work on installer
refactoring).

Since my C skills are not at the level I would like them to be it would be nice
if you point out even the tiniest mistakes, risky code or non-idiomatic usage.

Also the test case `test_retrieval_using_plain_ldap` fails due to unsuccesful
simple bind. I wanted to implement StartTLS for simple binds over ldap://, but
I get the following errors in dirsrv error log:

        [01/Nov/2016:10:44:52.395126000 +0000] connection - conn=883 fd=135
        Incoming BER Element was 3 bytes, max allowable is 209715200 bytes.
        Change the nsslapd-maxbersize attribute in cn=config to increase.

I guess there is something fishy with the way I initialize the StartTLS
session. I would appreciate your help with it.
"""

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 463fea6370bee56ef445056146099051dcc0f7cd 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/5] 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..e46c78d 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", 'c', 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..6e079ac 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\-c|\-\-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\-c, \-\-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 762daa61146f5f9f84d76518f20547e6d9620ccb 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/5] 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 | 160 +++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 127 insertions(+), 33 deletions(-)

diff --git a/client/ipa-getkeytab.c b/client/ipa-getkeytab.c
index e46c78d..5250f5b 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,26 +158,48 @@ 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 = 0;
+    char *scheme = NULL;
+
+    if (!mech) {
+        scheme = "ldaps";
+        port = 636;
+    } else if (strcmp(mech, LDAP_SASL_GSSAPI) == 0) {
+        scheme = "ldap";
+        port = 389;
+    }
+    else {
+        return 1;
+    }
+    url_len = snprintf(url,0,"%s://%s:%d",scheme,servername,port) +1;
 
-	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 = (char *)malloc (url_len);
+    if (!url){
+        fprintf(stderr, _("Out of memory \n"));
+        return LDAP_NO_MEMORY;
+    }
+    sprintf(url,"%s://%s:%d",scheme,servername,port);
+    *ldap_uri = url;
+    return 0;
+}
+
+static int ipa_ldap_init(LDAP **ld, const char *ldap_uri)
+{
+    int rc = 0;
+    rc = ldap_initialize(ld, ldap_uri);
 
-	free(url);
 	return rc;
 }
 
-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_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;
@@ -173,9 +216,9 @@ static int ipa_ldap_bind(const char *server_name, krb5_principal bind_princ,
             return ret;
         }
 
-        ret = ipa_ldap_init(&ld, "ldaps", server_name, 636);
+        ret = ipa_ldap_init(&ld, ldap_uri);
         if (ret != LDAP_SUCCESS) {
-            fprintf(stderr, _("Unable to init for ldaps(636) connection\n"));
+            fprintf(stderr, _("Unable to init connection to %s\n"), ldap_uri);
             return ret;
         }
 
@@ -186,9 +229,9 @@ static int ipa_ldap_bind(const char *server_name, krb5_principal bind_princ,
             goto done;
         }
     } else {
-        ret = ipa_ldap_init(&ld, "ldap", server_name, 389);
+        ret = ipa_ldap_init(&ld, ldap_uri);
         if (ret != LDAP_SUCCESS) {
-            fprintf(stderr, _("Unable to init for ldap(389) connection\n"));
+            fprintf(stderr, _("Unable to init connection to %s\n"), ldap_uri);
             return ret;
         }
     }
@@ -214,7 +257,7 @@ static int ipa_ldap_bind(const char *server_name, krb5_principal bind_princ,
 	goto done;
     }
 
-    if (bind_dn) {
+    if (!mech) {
         bv.bv_val = discard_const(bind_pw);
         bv.bv_len = strlen(bind_pw);
 
@@ -225,9 +268,15 @@ static int ipa_ldap_bind(const char *server_name, krb5_principal bind_princ,
             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);
@@ -339,12 +388,13 @@ 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 *ca_cert_file,
+			   const char *mech,
+			   const char *ca_cert_file,
 			   struct keys_container *keys)
 {
 	LDAP *ld = NULL;
@@ -371,7 +421,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 +549,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 +583,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 +739,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 +772,12 @@ int main(int argc, const char *argv[])
               _("LDAP password"), _("password to use if not using kerberos") },
 	    { "cacert", 'c', 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 +849,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(10);
+    }
+
+    if (sasl_mech && check_sasl_mech(sasl_mech)) {
+        fprintf(stderr, _("Invalid SASL bind mechanism\n"));
+        if (!quiet)
+            poptPrintUsage(pc, stderr, 0);
+        exit(10);
+    }
+
+    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(10);
+    }
+
+    if (!server && !ldap_uri) {
         struct ipa_config *ipacfg = NULL;
 
         ret = read_ipa_config(&ipacfg);
@@ -804,6 +890,14 @@ int main(int argc, const char *argv[])
             exit(2);
         }
     }
+    if (server) {
+        ret = ipa_server_to_uri(server, sasl_mech, &ldap_uri);
+        if (ret) {
+            fprintf(stderr, _("Incompatible combination of server name and "
+                              "SASL bind mechanism\n"));
+            exit(10);
+        }
+    }
 
     if (!ca_cert_file) {
         ca_cert_file = DEFAULT_CA_CERT_FILE;
@@ -837,7 +931,7 @@ int main(int argc, const char *argv[])
 		exit(4);
 	}
 
-	if (NULL == bindpw) {
+	if (NULL == bindpw && strncmp(sasl_mech, LDAP_SASL_GSSAPI, 79) == 0) {
 		krberr = krb5_cc_default(krbctx, &ccache);
 		if (krberr) {
 			fprintf(stderr,
@@ -863,8 +957,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 +983,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) {

From 091afd6283ba285be2435bb8665716b27c383ea9 Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Tue, 1 Nov 2016 11:58:16 +0100
Subject: [PATCH 3/5] ipa-getkeytab: use StartTLS connection over ldap://

In order to maintain the confidentiality of the operation initiate StartTLS
connection over port 389 when simple bind is performed.

https://fedorahosted.org/freeipa/ticket/6409
---
 client/ipa-getkeytab.c | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/client/ipa-getkeytab.c b/client/ipa-getkeytab.c
index 5250f5b..678f936 100644
--- a/client/ipa-getkeytab.c
+++ b/client/ipa-getkeytab.c
@@ -196,6 +196,21 @@ static int ipa_ldap_init(LDAP **ld, const char *ldap_uri)
 	return rc;
 }
 
+static int ipa_starttls_init(LDAP *ld, const char *ldap_uri)
+{
+    int rc = LDAP_SUCCESS;
+    char *startswith_ldap = NULL;
+
+    startswith_ldap = strstr("ldap://";, ldap_uri);
+
+    if (NULL != startswith_ldap) {
+        /* initialize StartTLS connection over ldap:// */
+        rc = ldap_start_tls_s(ld, NULL, NULL);
+    }
+    return rc;
+
+}
+
 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,
@@ -228,6 +243,13 @@ static int ipa_ldap_bind(const char *ldap_uri, krb5_principal bind_princ,
             fprintf(stderr, _("Unable to set LDAP_OPT_X_TLS\n"));
             goto done;
         }
+
+        ret = ipa_starttls_init(ld, ldap_uri);
+        if (ret != LDAP_SUCCESS) {
+            fprintf(stderr,
+                    _("Unable to init STARTTLS session to %s\n"), ldap_uri);
+            return ret;
+        }
     } else {
         ret = ipa_ldap_init(&ld, ldap_uri);
         if (ret != LDAP_SUCCESS) {

From f68226295ce507daccf461bbe78314528b09fff0 Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Mon, 31 Oct 2016 12:30:34 +0100
Subject: [PATCH 4/5] 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 10a844a29bfc1c7c7f4374be28bee722b06dd07f Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Mon, 31 Oct 2016 13:58:47 +0100
Subject: [PATCH 5/5] 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 | 197 +++++++++++++++++++++++++++--
 1 file changed, 185 insertions(+), 12 deletions(-)

diff --git a/ipatests/test_cmdline/test_ipagetkeytab.py b/ipatests/test_cmdline/test_ipagetkeytab.py
index 1f4581a..ef01935 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,151 @@ 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),
+                '-c', self.ca_cert])
+
+    def test_ldap_uri_server_raises_error(self, test_service):
+        test_service.ensure_exists()
+
+        self.assert_failure(
+            10,
+            "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(
+            10,
+            "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(
+            10,
+            "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)
+
+    def test_external_server_name_raises_error(self, test_service):
+        test_service.ensure_exists()
+
+        self.assert_failure(
+            10,
+            "Incompatible combination of server name and SASL bind mechanism",
+            test_service.name,
+            args=[
+                '-s', 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