ef3f20
From 29ed6e1c54a6ffbc3017660af5e2a81850e46b43 Mon Sep 17 00:00:00 2001
ef3f20
From: Lars Kellogg-Stedman <lars@redhat.com>
ef3f20
Date: Mon, 10 Apr 2017 15:52:37 -0400
ef3f20
Subject: [PATCH] util: teach write_file about copy_mode option
ef3f20
ef3f20
On centos/fedora/rhel/derivatives, /etc/ssh/sshd_config has mode 0600,
ef3f20
but cloud-init unilaterally sets file modes to 0644 when no explicit
ef3f20
mode is passed to util.write_file. On ubuntu/debian, this file has
ef3f20
mode 0644.  With this patch, write_file learns about the copy_mode
ef3f20
option, which will cause it to use the mode of the existing file by
ef3f20
default, falling back to the explicit mode parameter if the file does
ef3f20
not exist.
ef3f20
ef3f20
LP: #1644064
ef3f20
Resolves: rhbz#1295984
ef3f20
(cherry picked from commit 721348a622a660b65acfdf7fdf53203b47f80748)
ef3f20
---
ef3f20
 cloudinit/atomic_helper.py           | 12 +++++++++++-
ef3f20
 cloudinit/config/cc_set_passwords.py |  3 ++-
ef3f20
 cloudinit/util.py                    | 10 +++++++++-
ef3f20
 tests/unittests/test_util.py         | 33 +++++++++++++++++++++++++++++++--
ef3f20
 4 files changed, 53 insertions(+), 5 deletions(-)
ef3f20
ef3f20
diff --git a/cloudinit/atomic_helper.py b/cloudinit/atomic_helper.py
ef3f20
index fb2df8d..587b994 100644
ef3f20
--- a/cloudinit/atomic_helper.py
ef3f20
+++ b/cloudinit/atomic_helper.py
ef3f20
@@ -2,13 +2,23 @@
ef3f20
 
ef3f20
 import json
ef3f20
 import os
ef3f20
+import stat
ef3f20
 import tempfile
ef3f20
 
ef3f20
 _DEF_PERMS = 0o644
ef3f20
 
ef3f20
 
ef3f20
-def write_file(filename, content, mode=_DEF_PERMS, omode="wb"):
ef3f20
+def write_file(filename, content, mode=_DEF_PERMS,
ef3f20
+               omode="wb", copy_mode=False):
ef3f20
     # open filename in mode 'omode', write content, set permissions to 'mode'
ef3f20
+
ef3f20
+    if copy_mode:
ef3f20
+        try:
ef3f20
+            file_stat = os.stat(filename)
ef3f20
+            mode = stat.S_IMODE(file_stat.st_mode)
ef3f20
+        except OSError:
ef3f20
+            pass
ef3f20
+
ef3f20
     tf = None
ef3f20
     try:
ef3f20
         tf = tempfile.NamedTemporaryFile(dir=os.path.dirname(filename),
ef3f20
diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py
ef3f20
index cf1f59e..2745df8 100755
ef3f20
--- a/cloudinit/config/cc_set_passwords.py
ef3f20
+++ b/cloudinit/config/cc_set_passwords.py
ef3f20
@@ -174,7 +174,8 @@ def handle(_name, cfg, cloud, log, args):
ef3f20
                                                      pw_auth))
ef3f20
 
ef3f20
         lines = [str(l) for l in new_lines]
ef3f20
-        util.write_file(ssh_util.DEF_SSHD_CFG, "\n".join(lines))
ef3f20
+        util.write_file(ssh_util.DEF_SSHD_CFG, "\n".join(lines),
ef3f20
+                        copy_mode=True)
ef3f20
 
ef3f20
         try:
ef3f20
             cmd = cloud.distro.init_cmd  # Default service
ef3f20
diff --git a/cloudinit/util.py b/cloudinit/util.py
ef3f20
index 5725129..f90653d 100644
ef3f20
--- a/cloudinit/util.py
ef3f20
+++ b/cloudinit/util.py
ef3f20
@@ -1732,7 +1732,7 @@ def chmod(path, mode):
ef3f20
             os.chmod(path, real_mode)
ef3f20
 
ef3f20
 
ef3f20
-def write_file(filename, content, mode=0o644, omode="wb"):
ef3f20
+def write_file(filename, content, mode=0o644, omode="wb", copy_mode=False):
ef3f20
     """
ef3f20
     Writes a file with the given content and sets the file mode as specified.
ef3f20
     Resotres the SELinux context if possible.
ef3f20
@@ -1742,6 +1742,14 @@ def write_file(filename, content, mode=0o644, omode="wb"):
ef3f20
     @param mode: The filesystem mode to set on the file.
ef3f20
     @param omode: The open mode used when opening the file (w, wb, a, etc.)
ef3f20
     """
ef3f20
+
ef3f20
+    if copy_mode:
ef3f20
+        try:
ef3f20
+            file_stat = os.stat(filename)
ef3f20
+            mode = stat.S_IMODE(file_stat.st_mode)
ef3f20
+        except OSError:
ef3f20
+            pass
ef3f20
+
ef3f20
     ensure_dir(os.path.dirname(filename))
ef3f20
     if 'b' in omode.lower():
ef3f20
         content = encode_text(content)
ef3f20
diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py
ef3f20
index ab74311..5d21b4b 100644
ef3f20
--- a/tests/unittests/test_util.py
ef3f20
+++ b/tests/unittests/test_util.py
ef3f20
@@ -103,8 +103,8 @@ class TestWriteFile(helpers.TestCase):
ef3f20
         self.assertTrue(os.path.isdir(dirname))
ef3f20
         self.assertTrue(os.path.isfile(path))
ef3f20
 
ef3f20
-    def test_custom_mode(self):
ef3f20
-        """Verify custom mode works properly."""
ef3f20
+    def test_explicit_mode(self):
ef3f20
+        """Verify explicit file mode works properly."""
ef3f20
         path = os.path.join(self.tmp, "NewFile.txt")
ef3f20
         contents = "Hey there"
ef3f20
 
ef3f20
@@ -115,6 +115,35 @@ class TestWriteFile(helpers.TestCase):
ef3f20
         file_stat = os.stat(path)
ef3f20
         self.assertEqual(0o666, stat.S_IMODE(file_stat.st_mode))
ef3f20
 
ef3f20
+    def test_copy_mode_no_existing(self):
ef3f20
+        """Verify that file is created with mode 0o644 if copy_mode
ef3f20
+        is true and there is no prior existing file."""
ef3f20
+        path = os.path.join(self.tmp, "NewFile.txt")
ef3f20
+        contents = "Hey there"
ef3f20
+
ef3f20
+        util.write_file(path, contents, copy_mode=True)
ef3f20
+
ef3f20
+        self.assertTrue(os.path.exists(path))
ef3f20
+        self.assertTrue(os.path.isfile(path))
ef3f20
+        file_stat = os.stat(path)
ef3f20
+        self.assertEqual(0o644, stat.S_IMODE(file_stat.st_mode))
ef3f20
+
ef3f20
+    def test_copy_mode_with_existing(self):
ef3f20
+        """Verify that file is created using mode of existing file
ef3f20
+        if copy_mode is true."""
ef3f20
+        path = os.path.join(self.tmp, "NewFile.txt")
ef3f20
+        contents = "Hey there"
ef3f20
+
ef3f20
+        open(path, 'w').close()
ef3f20
+        os.chmod(path, 0o666)
ef3f20
+
ef3f20
+        util.write_file(path, contents, copy_mode=True)
ef3f20
+
ef3f20
+        self.assertTrue(os.path.exists(path))
ef3f20
+        self.assertTrue(os.path.isfile(path))
ef3f20
+        file_stat = os.stat(path)
ef3f20
+        self.assertEqual(0o666, stat.S_IMODE(file_stat.st_mode))
ef3f20
+
ef3f20
     def test_custom_omode(self):
ef3f20
         """Verify custom omode works properly."""
ef3f20
         path = os.path.join(self.tmp, "NewFile.txt")