From 5cdf0c9c5d238889a8e7f2f4c4c6c097bb6f2cdd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabiano=20Fid=C3=AAncio?= <fidencio@redhat.com>
Date: Fri, 5 Jul 2019 10:31:27 +0200
Subject: [PATCH] Don't expose the root_password via command line
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Instead of exposing the root-password in the command line, let's just
improve our way of handling the option and also accept a 'file:'
selector.

Together with this change, let's warn the users that using
--root-password in the old manner is not recommended and that the
'file:' selector should be used instead.

Reviewed-by: Radostin Stoyanov <rstoyanov1@gmail.com>
Signed-off-by: Fabiano Fidêncio <fidencio@redhat.com>
---
 man/virt-bootstrap.pod              | 16 ++++++++++------
 src/virtBootstrap/__init__.py       |  2 +-
 src/virtBootstrap/virt_bootstrap.py | 14 ++++++++++++++
 tests/__init__.py                   |  8 +++++++-
 tests/docker_source.py              |  4 ++--
 tests/file_source.py                |  4 ++--
 tests/password.txt                  |  1 +
 tests/virt_builder_source.py        |  4 ++--
 8 files changed, 39 insertions(+), 14 deletions(-)
 create mode 100644 tests/password.txt

diff --git a/man/virt-bootstrap.pod b/man/virt-bootstrap.pod
index c4e3f20..daca202 100644
--- a/man/virt-bootstrap.pod
+++ b/man/virt-bootstrap.pod
@@ -52,10 +52,13 @@ temporary directory used by virt-bootstrap or default C</tmp> will be used.
 Output format of the root file system. Possible values are B<dir>
 (default) and B<qcow2>.
 
-=item B<--root-password> ROOT_PASSWORD
+=item B<--root-password> SELECTOR
 
-This argument will generate hash from B<ROOT_PASSWORD> and insert the
-hashed value into C</etc/shadow> in the created root file system.
+This argument will generate hash from B<ROOT_PASSWORD>, gotten from
+the B<SELECTOR> field, and insert the hashed value into C</etc/shadow>
+in the created root file system.
+
+Note that B<SELECTOR> can be one of the following: "file:".
 
 If the output format is C<qcow2> these modifications are applied in
 additional qcow2 disk image.
@@ -192,14 +195,15 @@ B<0>-B<9> to B<1000>-B<1009> and B<500>-B<509> to B<1500>-B<1509>
 =item Set root password
 
     $ virt_bootstrap.py docker://opensuse /tmp/foo \
-      --root-password secret
+      --root-password file:/tmp/secret
 
 The above command will download the C<opensuse> container image and
 extract the root file system to C</tmp/foo>. Then it will generate hash
-of the string C<secret> and insert it into C</tmp/foo/etc/shadow> file.
+of the password present in C</tmp/secret> file and insert it into
+C</tmp/foo/etc/shadow> file.
 
     $ virt_bootstrap.py docker://opensuse /tmp/foo \
-      --root-password secret \
+      --root-password file:/tmp/secret \
       -f qcow2
 
 Similarly for B<qcow2> format the container image will be downloaded and
diff --git a/src/virtBootstrap/__init__.py b/src/virtBootstrap/__init__.py
index 483bfab..b23d543 100644
--- a/src/virtBootstrap/__init__.py
+++ b/src/virtBootstrap/__init__.py
@@ -152,7 +152,7 @@ Usage Examples
     virtBootstrap.bootstrap(
         uri='docker://fedora',
         dest='/tmp/foo',
-        root_password='secret'
+        root_password='file:/tmp/secret'
     )
 
     # Convert Ubuntu container image to qcow2 disk image using backing chains
diff --git a/src/virtBootstrap/virt_bootstrap.py b/src/virtBootstrap/virt_bootstrap.py
index 61d6cab..ad0b3e6 100755
--- a/src/virtBootstrap/virt_bootstrap.py
+++ b/src/virtBootstrap/virt_bootstrap.py
@@ -148,6 +148,20 @@ def bootstrap(uri, dest,
     if gid_map is None:
         gid_map = []
 
+
+    if root_password:
+        if root_password.startswith('file:'):
+            root_password_file = root_password[len('file:'):]
+            logger.debug("Reading root password from file %s" %
+                         root_password_file)
+            with open(root_password_file) as pwdfile:
+                root_password = pwdfile.readline().rstrip("\n\r")
+        else:
+            logger.warning(_("Passing the root_password directly via command "
+                             "line is deprecated and using the 'file:' "
+                             "selector is the recommended way to use this "
+                             "option."))
+
     source(uri=uri,
            fmt=fmt,
            username=username,
diff --git a/tests/__init__.py b/tests/__init__.py
index 8fc46c4..ce0d5fa 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -334,9 +334,15 @@ class ImageAccessor(unittest.TestCase):
         Note: For simplicity we assume that the first line of /etc/shadow
         contains the root entry.
         """
+        root_password = self.root_password
+        if root_password and root_password.startswith('file:'):
+            root_password_file = root_password[len('file:'):]
+            with open(root_password_file) as pwdfile:
+                root_password = pwdfile.readline().rstrip("\n\r")
+
         self.assertTrue(
             passlib.hosts.linux_context.verify(
-                self.root_password,
+                root_password,
                 shadow_content[0].split(':')[1]
             ),
             "Invalid root password hash."
diff --git a/tests/docker_source.py b/tests/docker_source.py
index 585b9d2..ea7e065 100644
--- a/tests/docker_source.py
+++ b/tests/docker_source.py
@@ -156,7 +156,7 @@ class TestDirDockerSource(ImageAccessor):
         Ensures that the root password is set correctly.
         """
         layers = CreateLayers(self.tar_file, self.rootfs_tree, self.tar_dir)
-        self.root_password = "My secret root password"
+        self.root_password = "file:tests/password.txt"
         self.call_bootstrap(layers.generate_manifest())
         self.validate_shadow_file()
 
@@ -282,7 +282,7 @@ class TestQcow2DockerSource(Qcow2ImageAccessor):
         """
         Ensures that the root password is set in the last qcow2 image.
         """
-        self.root_password = "My secret password"
+        self.root_password = "file:tests/password.txt"
         layers_rootfs = self.call_bootstrap()
 
         g = guestfs.GuestFS(python_return_dict=True)
diff --git a/tests/file_source.py b/tests/file_source.py
index 4554ecd..5c1e416 100644
--- a/tests/file_source.py
+++ b/tests/file_source.py
@@ -74,7 +74,7 @@ class TestDirFileSource(ImageAccessor):
         Ensures that the root password is set correctly when FileSource is used
         with fmt='dir'.
         """
-        self.root_password = 'my secret root password'
+        self.root_password = 'file:tests/password.txt'
         self.call_bootstrap()
         self.validate_shadow_file()
 
@@ -120,7 +120,7 @@ class TestQcow2FileSource(Qcow2ImageAccessor):
         """
         Ensures that the root password is set in the last qcow2 image.
         """
-        self.root_password = "My secret password"
+        self.root_password = "file:tests/password.txt"
         self.call_bootstrap()
         self.check_image = self.validate_shadow_file_in_image
         self.check_qcow2_images(self.get_image_path(1))
diff --git a/tests/password.txt b/tests/password.txt
new file mode 100644
index 0000000..8a76848
--- /dev/null
+++ b/tests/password.txt
@@ -0,0 +1 @@
+my secret root password
diff --git a/tests/virt_builder_source.py b/tests/virt_builder_source.py
index c06d3ed..5da876e 100644
--- a/tests/virt_builder_source.py
+++ b/tests/virt_builder_source.py
@@ -206,7 +206,7 @@ class TestVirtBuilderSource(Qcow2ImageAccessor):
         """
         Ensures that password for root is set correctly.
         """
-        self.root_password = 'my secret root password'
+        self.root_password = 'file:tests/password.txt'
         self.fmt = 'dir'
         self.call_bootstrap()
         self.validate_shadow_file()
@@ -237,7 +237,7 @@ class TestVirtBuilderSource(Qcow2ImageAccessor):
         "layer-1.qcow2"
         """
         self.fmt = 'qcow2'
-        self.root_password = "My secret password"
+        self.root_password = "file:tests/password.txt"
         self.call_bootstrap()
         self.check_image = self.validate_shadow_file_in_image
         self.check_qcow2_images(self.get_image_path())
-- 
2.21.0

