Skip to content

Commit 47858c5

Browse files
cephadm: simplify loading jinja2 templates from zipapp
The saga of failures in teuthology brought on by the combination of zipapp and jinja2 template loading continues in this episode. Remove some of the path handling that was taken from the original jinja2 implementation and try to replace it with something inherently simpler such that we always construct paths relative to the zip archive and pass that to the zipimporter. The hope here is that by doing fewer manipulations of the "path" we are more likely to match the items tracked inside the zipimporter class. We do not worry about some of the concerns that regular jinja2 has as we never expect cephadm to be run on something other than linux and we have control over what sources we expect to load templates from. We do take one precaution and that is to reject any paths that contain "." and ".." as components. Less to avoid malicious attempts to read files it should not (but that would be nice) and more to nudge toward simple template references recorded in our Templates enum. Signed-off-by: John Mulligan <[email protected]>
1 parent 1680e46 commit 47858c5

File tree

1 file changed

+12
-25
lines changed

1 file changed

+12
-25
lines changed

src/cephadm/cephadmlib/templating.py

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,27 +34,18 @@ def __init__(
3434
self,
3535
template: str,
3636
*,
37-
path: str = '',
3837
relative_path: str = '',
39-
archive_norm_path: str = '',
40-
archive_path: str = ''
38+
archive_path: str = '',
4139
) -> None:
4240
super().__init__(template)
43-
self.path = path
4441
self.relative_path = relative_path
45-
self.archive_norm_path = archive_norm_path
4642
self.archive_path = archive_path
4743

4844
def __str__(self) -> str:
49-
msg = self.message
50-
msg2 = ''
51-
if self.path or self.relative_path:
52-
msg2 += f' path [{self.path!r}, rel={self.relative_path!r}] not found'
53-
if self.archive_norm_path or self.archive_path:
54-
msg2 += f' in [{self.archive_norm_path!r}, orig={self.archive_path!r}]'
55-
if msg2:
56-
msg2 = ':' + msg2
57-
return f'{msg}{msg2}'
45+
return (
46+
f'{self.message}: path {self.relative_path!r}'
47+
f' not found in {self.archive_path!r}'
48+
)
5849

5950

6051
class _PackageLoader(jinja2.PackageLoader):
@@ -86,27 +77,23 @@ def get_source(
8677

8778
def _get_archive_source(self, template: str) -> Tuple[str, str, None]:
8879
assert isinstance(self._loader, zipimport.zipimporter)
89-
path = arelpath = os.path.normpath(
90-
posixpath.join(
91-
self._template_root,
92-
*jinja2.loaders.split_template_path(template)
93-
)
80+
arelpath = posixpath.join(
81+
self.package_name, self.package_path, template
9482
)
95-
archive_path = os.path.normpath(self._loader.archive)
96-
if arelpath.startswith(archive_path + '/'):
97-
plen = len(archive_path) + 1
98-
arelpath = arelpath[plen:]
83+
if any(p == '.' or p == '..' for p in arelpath.split(posixpath.sep)):
84+
raise ValueError('template path contains invalid components')
9985
try:
10086
source = cast(bytes, self._loader.get_data(arelpath))
10187
except OSError as e:
10288
not_found = TemplateNotFoundInZipApp(
10389
template,
104-
path=path,
10590
relative_path=arelpath,
106-
archive_norm_path=archive_path,
10791
archive_path=self._loader.archive,
10892
)
10993
raise not_found from e
94+
path = os.path.normpath(
95+
posixpath.join(self._loader.archive, arelpath)
96+
)
11097
return source.decode(self.encoding), path, None
11198

11299

0 commit comments

Comments
 (0)