Merge lp:~stub/charm-helpers/bug-1478940-dont-reset-directory-perms into lp:charm-helpers

Proposed by Stuart Bishop
Status: Merged
Merged at revision: 472
Proposed branch: lp:~stub/charm-helpers/bug-1478940-dont-reset-directory-perms
Merge into: lp:charm-helpers
Diff against target: 61 lines (+41/-1)
2 files modified
charmhelpers/core/templating.py (+5/-1)
tests/core/test_templating.py (+36/-0)
To merge this branch: bzr merge lp:~stub/charm-helpers/bug-1478940-dont-reset-directory-perms
Reviewer Review Type Date Requested Status
charmers Pending
Review via email: mp+266088@code.launchpad.net

Description of the change

Rendering a template should not make the target directory world readable as a side effect.

To post a comment you must log in.
Revision history for this message
Stuart Bishop (stub) wrote :

I'm going to land this if there are no objections. The security bug has been pending for a few months.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/core/templating.py'
2--- charmhelpers/core/templating.py 2015-04-29 12:52:18 +0000
3+++ charmhelpers/core/templating.py 2015-07-28 12:54:49 +0000
4@@ -64,5 +64,9 @@
5 level=hookenv.ERROR)
6 raise e
7 content = template.render(context)
8- host.mkdir(os.path.dirname(target), owner, group, perms=0o755)
9+ target_dir = os.path.dirname(target)
10+ if not os.path.exists(target_dir):
11+ # This is a terrible default directory permission, as the file
12+ # or its siblings will often contain secrets.
13+ host.mkdir(os.path.dirname(target), owner, group, perms=0o755)
14 host.write_file(target, content.encode(encoding), owner, group, perms)
15
16=== modified file 'tests/core/test_templating.py'
17--- tests/core/test_templating.py 2015-04-29 12:52:18 +0000
18+++ tests/core/test_templating.py 2015-07-28 12:54:49 +0000
19@@ -53,6 +53,42 @@
20 contents = open(fn2.name).read()
21 self.assertRegexpMatches(contents, 'listen 80')
22 self.assertEqual(fchown.call_count, 2)
23+ # Not called, because the target directory exists. Calling
24+ # it would make the target directory world readable and
25+ # expose your secrets (!).
26+ self.assertEqual(mkdir.call_count, 0)
27+
28+ @mock.patch.object(templating.os.path, 'exists')
29+ @mock.patch.object(templating.host.os, 'fchown')
30+ @mock.patch.object(templating.host, 'mkdir')
31+ @mock.patch.object(templating.host, 'log')
32+ def test_render_no_dir(self, log, mkdir, fchown, exists):
33+ exists.return_value = False
34+ with tempfile.NamedTemporaryFile() as fn1, \
35+ tempfile.NamedTemporaryFile() as fn2:
36+ context = {
37+ 'nats': {
38+ 'port': '1234',
39+ 'host': 'example.com',
40+ },
41+ 'router': {
42+ 'domain': 'api.foo.com'
43+ },
44+ 'nginx_port': 80,
45+ }
46+ templating.render('fake_cc.yml', fn1.name,
47+ context, templates_dir=TEMPLATES_DIR)
48+ contents = open(fn1.name).read()
49+ self.assertRegexpMatches(contents, 'port: 1234')
50+ self.assertRegexpMatches(contents, 'host: example.com')
51+ self.assertRegexpMatches(contents, 'domain: api.foo.com')
52+
53+ templating.render('test.conf', fn2.name, context,
54+ templates_dir=TEMPLATES_DIR)
55+ contents = open(fn2.name).read()
56+ self.assertRegexpMatches(contents, 'listen 80')
57+ self.assertEqual(fchown.call_count, 2)
58+ # Target directory was created, world readable (!).
59 self.assertEqual(mkdir.call_count, 2)
60
61 @mock.patch.object(templating.host.os, 'fchown')

Subscribers

People subscribed via source and target branches