Change default privkey permissions while preserving group permissions (#6480)

Fixes #1473.

writes privkey.pem to 0600 by default for new lineages
on renewals where a new privkey is generated, preserves group mode and gid
Things this PR does not do:

we talked about forcing 0600 on privkeys when a Certbot upgrade is detected. Instead, this PR only creates new lineages with the more restrictive permission to prevent renewal breakages.
this doesn't solve many of the problems mentioned in #1473 that are not directly related to the title issue!

* safe_open on archive keyfiles

* keep group from current lineage

* clean up integration test

* safe_open can follow symlinks

* fix tests on windows, maybe

* Address Brad's comments

* Revert changes to safe_open
* Test chown is called when saving new key
* Reorder chown operation

* Changelog and documentation

* Fix documentation style
This commit is contained in:
sydneyli
2018-11-29 09:33:05 -08:00
committed by Brad Warren
parent 7527a6c959
commit 7d0ac47139
5 changed files with 98 additions and 7 deletions
+45
View File
@@ -13,6 +13,7 @@ import six
import certbot
from certbot import cli
from certbot import compat
from certbot import errors
from certbot.storage import ALL_FOUR
@@ -91,6 +92,8 @@ class BaseRenewableCertTest(test_util.ConfigTestCase):
link)
with open(link, "wb") as f:
f.write(kind.encode('ascii') if value is None else value)
if kind == "privkey":
os.chmod(link, 0o600)
def _write_out_ex_kinds(self):
for kind in ALL_FOUR:
@@ -543,6 +546,47 @@ class RenewableCertTests(BaseRenewableCertTest):
self.assertFalse(os.path.islink(self.test_rc.version("privkey", 10)))
self.assertFalse(os.path.exists(temp_config_file))
@test_util.broken_on_windows
@mock.patch("certbot.storage.relevant_values")
def test_save_successor_maintains_group_mode(self, mock_rv):
# Mock relevant_values() to claim that all values are relevant here
# (to avoid instantiating parser)
mock_rv.side_effect = lambda x: x
for kind in ALL_FOUR:
self._write_out_kind(kind, 1)
self.test_rc.update_all_links_to(1)
self.assertTrue(compat.compare_file_modes(
os.stat(self.test_rc.version("privkey", 1)).st_mode, 0o600))
os.chmod(self.test_rc.version("privkey", 1), 0o444)
# If no new key, permissions should be the same (we didn't write any keys)
self.test_rc.save_successor(1, b"newcert", None, b"new chain", self.config)
self.assertTrue(compat.compare_file_modes(
os.stat(self.test_rc.version("privkey", 2)).st_mode, 0o444))
# If new key, permissions should be rest to 600 + preserved group
self.test_rc.save_successor(2, b"newcert", b"new_privkey", b"new chain", self.config)
self.assertTrue(compat.compare_file_modes(
os.stat(self.test_rc.version("privkey", 3)).st_mode, 0o640))
# If permissions reverted, next renewal will also revert permissions of new key
os.chmod(self.test_rc.version("privkey", 3), 0o404)
self.test_rc.save_successor(3, b"newcert", b"new_privkey", b"new chain", self.config)
self.assertTrue(compat.compare_file_modes(
os.stat(self.test_rc.version("privkey", 4)).st_mode, 0o600))
@test_util.broken_on_windows
@mock.patch("certbot.storage.relevant_values")
@mock.patch("certbot.storage.os.chown")
def test_save_successor_maintains_gid(self, mock_chown, mock_rv):
# Mock relevant_values() to claim that all values are relevant here
# (to avoid instantiating parser)
mock_rv.side_effect = lambda x: x
for kind in ALL_FOUR:
self._write_out_kind(kind, 1)
self.test_rc.update_all_links_to(1)
self.test_rc.save_successor(1, b"newcert", None, b"new chain", self.config)
self.assertFalse(mock_chown.called)
self.test_rc.save_successor(2, b"newcert", b"new_privkey", b"new chain", self.config)
self.assertTrue(mock_chown.called)
def _test_relevant_values_common(self, values):
defaults = dict((option, cli.flag_default(option))
for option in ("authenticator", "installer",
@@ -629,6 +673,7 @@ class RenewableCertTests(BaseRenewableCertTest):
self.config.live_dir, "README")))
self.assertTrue(os.path.exists(os.path.join(
self.config.live_dir, "the-lineage.com", "README")))
self.assertTrue(compat.compare_file_modes(os.stat(result.key_path).st_mode, 0o600))
with open(result.fullchain, "rb") as f:
self.assertEqual(f.read(), b"cert" + b"chain")
# Let's do it again and make sure it makes a different lineage