URL: https://github.com/freeipa/freeipa/pull/757
Author: tomaskrizek
 Title: #757: ca, kra install: validate DM password
Action: synchronized

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/757/head:pr757
git checkout pr757
From 2cce2304491ce575b6803ca4dd7d8f6630c57a35 Mon Sep 17 00:00:00 2001
From: Tomas Krizek <tkri...@redhat.com>
Date: Wed, 3 May 2017 10:05:25 +0200
Subject: [PATCH 1/5] ca install: merge duplicated code for DM password

Extract copy-pasted code to a single function.

Related https://pagure.io/freeipa/issue/6892

Signed-off-by: Tomas Krizek <tkri...@redhat.com>
---
 install/tools/ipa-ca-install | 40 +++++++++++++++++-----------------------
 1 file changed, 17 insertions(+), 23 deletions(-)

diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install
index 60261aa..da6e5c3 100755
--- a/install/tools/ipa-ca-install
+++ b/install/tools/ipa-ca-install
@@ -116,9 +116,19 @@ def parse_options():
     return safe_options, options, filename
 
 
-def get_dirman_password():
-    return installutils.read_password(
-        "Directory Manager (existing master)", confirm=False, validate=False)
+def _get_dirman_password(password=None, unattended=False):
+    if not password:
+        if unattended:
+            sys.exit('Directory Manager password required')
+        try:
+            password = installutils.read_password(
+                "Directory Manager (existing master)", confirm=False,
+                validate=False)
+        except KeyboardInterrupt:
+            sys.exit(0)
+        if password is None:
+            sys.exit("Directory Manager password required")
+    return password
 
 
 def install_replica(safe_options, options, filename):
@@ -142,16 +152,8 @@ def install_replica(safe_options, options, filename):
         check_creds(options, api.env.realm)
 
     # get the directory manager password
-    dirman_password = options.password
-    if not dirman_password:
-        if options.unattended:
-            sys.exit('Directory Manager password required')
-        try:
-            dirman_password = get_dirman_password()
-        except KeyboardInterrupt:
-            sys.exit(0)
-        if dirman_password is None:
-            sys.exit("Directory Manager password required")
+    dirman_password = _get_dirman_password(
+        options.password, options.unattended)
 
     if (not options.promote and not options.admin_password and
             not options.skip_conncheck and options.unattended):
@@ -199,16 +201,8 @@ def install_replica(safe_options, options, filename):
 
 
 def install_master(safe_options, options):
-    dm_password = options.password
-    if not dm_password:
-        if options.unattended:
-            sys.exit('Directory Manager password required')
-        try:
-            dm_password = get_dirman_password()
-        except KeyboardInterrupt:
-            sys.exit(0)
-        if dm_password is None:
-            sys.exit("Directory Manager password required")
+    dm_password = _get_dirman_password(
+        options.password, options.unattended)
 
     options.realm_name = api.env.realm
     options.domain_name = api.env.domain

From e3e8f051220970f10a34c8297b1a381d1721b663 Mon Sep 17 00:00:00 2001
From: Tomas Krizek <tkri...@redhat.com>
Date: Wed, 3 May 2017 10:01:09 +0200
Subject: [PATCH 2/5] installutils: add DM password validator

Add a validator that checks whether provided Directory Manager
is valid by attempting to connect to LDAP.

Related https://pagure.io/freeipa/issue/6892

Signed-off-by: Tomas Krizek <tkri...@redhat.com>
---
 ipaserver/install/installutils.py | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index 9230e70..b6f0148 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -50,6 +50,7 @@
 from ipapython import ipautil, admintool, version
 from ipapython.admintool import ScriptError
 from ipapython.ipa_log_manager import root_logger
+from ipapython.ipaldap import DIRMAN_DN, LDAPClient
 from ipalib.util import validate_hostname
 from ipalib import api, errors, x509
 from ipapython.dn import DN
@@ -329,6 +330,21 @@ def _read_password_default_validator(password):
     if len(password) < 8:
         raise ValueError("Password must be at least 8 characters long")
 
+
+def validate_dm_password_ldap(password):
+    """
+    Validate DM password by attempting to connect to LDAP. api.env has to
+    contain valid ldap_uri.
+    """
+    client = LDAPClient(api.env.ldap_uri, cacert=paths.IPA_CA_CRT)
+    try:
+        client.simple_bind(DIRMAN_DN, password)
+    except errors.ACIError:
+        raise ValueError("Invalid Directory Manager password")
+    else:
+        client.unbind()
+
+
 def read_password(user, confirm=True, validate=True, retry=True, validator=_read_password_default_validator):
     correct = False
     pwd = None

From 230e835dc64bd0ddf3d900efa8c34001da8f3221 Mon Sep 17 00:00:00 2001
From: Tomas Krizek <tkri...@redhat.com>
Date: Wed, 10 May 2017 09:36:52 +0200
Subject: [PATCH 3/5] kra install: use ScriptErrors instead RuntimeErrors

Replace RuntimeErrors with ScriptErrors to be consistent with the
rest of the code.

Related https://pagure.io/freeipa/issue/6892

Signed-off-by: Tomas Krizek <tkri...@redhat.com>
---
 ipaserver/install/ipa_kra_install.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/ipaserver/install/ipa_kra_install.py b/ipaserver/install/ipa_kra_install.py
index 8b1cb63..0da4f2e 100644
--- a/ipaserver/install/ipa_kra_install.py
+++ b/ipaserver/install/ipa_kra_install.py
@@ -138,8 +138,8 @@ def run(self):
         super(KRAInstaller, self).run()
 
         if not cainstance.is_ca_installed_locally():
-            raise RuntimeError("Dogtag CA is not installed. "
-                               "Please install the CA first")
+            raise admintool.ScriptError("Dogtag CA is not installed. "
+                                        "Please install the CA first")
 
         # check if KRA is not already installed
         _kra = krainstance.KRAInstance(api)
@@ -155,11 +155,11 @@ def run(self):
             if domain_level > DOMAIN_LEVEL_0:
                 self.options.promote = True
             elif not self.args:
-                raise RuntimeError("A replica file is required.")
+                raise admintool.ScriptError("A replica file is required.")
 
         if self.args and (not self.installing_replica or self.options.promote):
-            raise RuntimeError("Too many parameters provided. "
-                               "No replica file is required.")
+            raise admintool.ScriptError("Too many parameters provided. "
+                                        "No replica file is required.")
 
         self.options.dm_password = self.options.password
         self.options.setup_ca = False

From 8d13f2a638d254b3dd3b8045d43d7efeb4432e3b Mon Sep 17 00:00:00 2001
From: Tomas Krizek <tkri...@redhat.com>
Date: Wed, 3 May 2017 10:16:13 +0200
Subject: [PATCH 4/5] ca, kra install: validate DM password

Before proceeding with installation, validate DM password. If the
provided DM password is invalid, abort the installation.

Fixes https://pagure.io/freeipa/issue/6892

Signed-off-by: Tomas Krizek <tkri...@redhat.com>
---
 install/tools/ipa-ca-install         | 19 ++++++++++---------
 ipaserver/install/ipa_kra_install.py |  8 ++++++++
 2 files changed, 18 insertions(+), 9 deletions(-)

diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install
index da6e5c3..78c2d57 100755
--- a/install/tools/ipa-ca-install
+++ b/install/tools/ipa-ca-install
@@ -34,6 +34,7 @@ from ipaserver.install import cainstance, service
 from ipapython import version
 from ipalib import api
 from ipalib.constants import DOMAIN_LEVEL_0
+from ipapython.admintool import ScriptError
 from ipapython.config import IPAOptionParser
 from ipapython.ipa_log_manager import root_logger, standard_logging_setup
 from ipaplatform.paths import paths
@@ -119,15 +120,15 @@ def parse_options():
 def _get_dirman_password(password=None, unattended=False):
     if not password:
         if unattended:
-            sys.exit('Directory Manager password required')
-        try:
-            password = installutils.read_password(
-                "Directory Manager (existing master)", confirm=False,
-                validate=False)
-        except KeyboardInterrupt:
-            sys.exit(0)
-        if password is None:
-            sys.exit("Directory Manager password required")
+            raise ScriptError('Directory Manager password required')
+        password = installutils.read_password(
+            "Directory Manager (existing master)", confirm=False,
+            validate=False)
+    try:
+        installutils.validate_dm_password_ldap(password)
+    except ValueError:
+        raise ScriptError("Directory Manager password is invalid")
+
     return password
 
 
diff --git a/ipaserver/install/ipa_kra_install.py b/ipaserver/install/ipa_kra_install.py
index 0da4f2e..5b40267 100644
--- a/ipaserver/install/ipa_kra_install.py
+++ b/ipaserver/install/ipa_kra_install.py
@@ -137,6 +137,14 @@ def ask_for_options(self):
     def run(self):
         super(KRAInstaller, self).run()
 
+        # Verify DM password. This has to be called after ask_for_options(),
+        # so it can't be placed in validate_options().
+        try:
+            installutils.validate_dm_password_ldap(self.options.password)
+        except ValueError:
+            raise admintool.ScriptError(
+                "Directory Manager password is invalid")
+
         if not cainstance.is_ca_installed_locally():
             raise admintool.ScriptError("Dogtag CA is not installed. "
                                         "Please install the CA first")

From 46774281093a9b61cfaf7a7fdeed60031948ce4b Mon Sep 17 00:00:00 2001
From: Tomas Krizek <tkri...@redhat.com>
Date: Wed, 10 May 2017 10:28:36 +0200
Subject: [PATCH 5/5] tests: make sure CA and KRA can handle incorrect dm
 password

CA and KRA installation should be possible even if it has previously
failed due to an incorrect Directory Manager passoword.

Related https://pagure.io/freeipa/issue/6892

Signed-off-by: Tomas Krizek <tkri...@redhat.com>
---
 ipatests/pytest_plugins/integration/tasks.py   | 16 +++++++++++----
 ipatests/test_integration/test_installation.py | 28 ++++++++++++++++++++++++++
 2 files changed, 40 insertions(+), 4 deletions(-)

diff --git a/ipatests/pytest_plugins/integration/tasks.py b/ipatests/pytest_plugins/integration/tasks.py
index 172f5b8..ccd6017 100644
--- a/ipatests/pytest_plugins/integration/tasks.py
+++ b/ipatests/pytest_plugins/integration/tasks.py
@@ -1156,20 +1156,28 @@ def ipa_restore(master, backup_path):
                         backup_path])
 
 
-def install_kra(host, domain_level=None, first_instance=False, raiseonerr=True):
+def install_kra(host, domain_level=None, first_instance=False, raiseonerr=True,
+                incorrect_dm_password=False):
     if domain_level is None:
         domain_level = domainlevel(host)
-    command = ["ipa-kra-install", "-U", "-p", host.config.dirman_password]
+    dm_password = host.config.dirman_password
+    if incorrect_dm_password:
+        dm_password += "x"
+    command = ["ipa-kra-install", "-U", "-p", dm_password]
     if domain_level == DOMAIN_LEVEL_0 and not first_instance:
         replica_file = get_replica_filename(host)
         command.append(replica_file)
     return host.run_command(command, raiseonerr=raiseonerr)
 
 
-def install_ca(host, domain_level=None, first_instance=False, raiseonerr=True):
+def install_ca(host, domain_level=None, first_instance=False, raiseonerr=True,
+               incorrect_dm_password=False):
     if domain_level is None:
         domain_level = domainlevel(host)
-    command = ["ipa-ca-install", "-U", "-p", host.config.dirman_password,
+    dm_password = host.config.dirman_password
+    if incorrect_dm_password:
+        dm_password += "x"
+    command = ["ipa-ca-install", "-U", "-p", dm_password,
                "-P", 'admin', "-w", host.config.admin_password]
     if domain_level == DOMAIN_LEVEL_0 and not first_instance:
         replica_file = get_replica_filename(host)
diff --git a/ipatests/test_integration/test_installation.py b/ipatests/test_integration/test_installation.py
index b13c999..fa90a12 100644
--- a/ipatests/test_integration/test_installation.py
+++ b/ipatests/test_integration/test_installation.py
@@ -81,6 +81,8 @@ def test_replica2_ipa_ca_install(self):
         tasks.install_ca(self.replicas[2])
 
     def test_replica2_ipa_kra_install(self):
+        tasks.install_kra(self.replica[2], incorrect_dm_password=True,
+                          raiseonerr=False)
         tasks.install_kra(self.replicas[2])
 
 
@@ -312,3 +314,29 @@ def test_install_master(self):
 
     def test_install_kra(self):
         tasks.install_kra(self.master, first_instance=True)
+
+
+##
+# Single replica installation tests
+##
+
+class TestInstallReplicaCA_KRA_Incorrect_Password(IntegrationTest):
+
+    num_replicas = 1
+    topology = 'star'
+
+    @classmethod
+    def install(cls, mh):
+        tasks.install_master(cls.master, setup_dns=False)
+
+    def test_replica0_ipa_ca_install(self):
+        tasks.install_ca(self.replicas[0], incorrect_dm_password=True,
+                        raiseonerr=False)
+        tasks.install_ca(self.replicas[0])
+
+    def test_replica0_ipa_kra_install(self):
+        tasks.install_kra(self.replicas[0], incorrect_dm_password=True,
+                            raiseonerr=False)
+        tasks.install_kra(self.replicas[0])
+
+
-- 
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