Message from discussion
bdev: Add verification for file storage paths
Received: by 10.180.94.98 with SMTP id db2mr5767514wib.2.1351000475167;
Tue, 23 Oct 2012 06:54:35 -0700 (PDT)
X-BeenThere: ganeti-devel@googlegroups.com
Received: by 10.180.90.134 with SMTP id bw6ls11043830wib.1.canary; Tue, 23 Oct
2012 06:54:33 -0700 (PDT)
Received: by 10.180.84.234 with SMTP id c10mr3601898wiz.4.1351000473185;
Tue, 23 Oct 2012 06:54:33 -0700 (PDT)
Received: by 10.180.84.234 with SMTP id c10mr3601896wiz.4.1351000473174;
Tue, 23 Oct 2012 06:54:33 -0700 (PDT)
Return-Path: <han...@google.com>
Received: from mail-wi0-f202.google.com (mail-wi0-f202.google.com [209.85.212.202])
by gmr-mx.google.com with ESMTPS id bu8si1329874wib.2.2012.10.23.06.54.33
(version=TLSv1/SSLv3 cipher=OTHER);
Tue, 23 Oct 2012 06:54:33 -0700 (PDT)
Received-SPF: pass (google.com: domain of han...@google.com designates 209.85.212.202 as permitted sender) client-ip=209.85.212.202;
Authentication-Results: gmr-mx.google.com; spf=pass (google.com: domain of han...@google.com designates 209.85.212.202 as permitted sender) smtp.mail=han...@google.com; dkim=pass header...@google.com
Received: by mail-wi0-f202.google.com with SMTP id hr7so216763wib.3
for <ganeti-devel@googlegroups.com>; Tue, 23 Oct 2012 06:54:33 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=google.com; s=20120113;
h=from:to:subject:date:message-id:x-mailer:in-reply-to:references
:mime-version:content-type:content-transfer-encoding;
bh=pVKL5UWLI7HhtSMZ+qozXP0EJAg1ZlfySXjSFJzv+aI=;
b=m2inxyo9PBp4jZLIkQXwj2Hftksa/QFZG0yH8oTMOLqIY5gwub5xFljxA7IWrKo5dF
1D9hQsCHt0NKsm3EXIMFJEP3Rr7LESxNe3D5D5fal+Nu0R97BjezObtKYWUcK8KQfeYl
3WgC9vxj75eE65f8ar/f2ttjd0wTz5bQFim4eReqZMX8Sfak34k3bI3F97SVGB+JmITt
Jf+/sqBClL7JDh/1I5i+p5qfJs49HmDdu+4ldRhor0M8hsABSAHZmw3bOUHokRFObObm
weY0n0r4HF6b5R3LF0/TYuiP/d0NbcWAc6L1AZyC8F4beocOG8SdlBI6jlOmV2+VLV5f
PvQA==
d=google.com; s=20120113;
h=from:to:subject:date:message-id:x-mailer:in-reply-to:references
:mime-version:content-type:content-transfer-encoding
:x-gm-message-state;
bh=pVKL5UWLI7HhtSMZ+qozXP0EJAg1ZlfySXjSFJzv+aI=;
b=pXISC58Hp0223souJUb0WAjevnacRFxN75tXvSdLb8smwG3eivF6C0mtbYyJJX/l0V
HwI96Sot5i1Ep/J8af8kORlRcJzyckMnyxY+SKOin8EW37Rd42VDY4x4d0Mg3uA4OyxT
fCpu2kn6uKGpX/kImeSC8OKB4xG3fUZ20ic3wsMB5PR0n68WhBig1Js7N0fpZttf+XS9
8pTcAxAgVs4a+3vEy3Ay97mwkaBAAiO24jUrgdrOs/28zvfvUtt1RK/xHEuzV5SoRuOt
nTADbF6ImgsWZAx8DKpqTgj3o++1xzbvu0lkQ2tQJyHzOI9EDblEZQgbqlAp1ZlmaIAq
zPIg==
Received: by 10.216.202.93 with SMTP id c71mr785583weo.3.1351000473013;
Tue, 23 Oct 2012 06:54:33 -0700 (PDT)
Return-Path: <han...@google.com>
Received: from hpza9.eem.corp.google.com ([74.125.121.33])
by gmr-mx.google.com with ESMTPS id z7si123079wiw.1.2012.10.23.06.54.32
(version=TLSv1/SSLv3 cipher=AES128-SHA);
Tue, 23 Oct 2012 06:54:33 -0700 (PDT)
Received: from hpgntaa-ubiq38.eem.corp.google.com (hpgntaa-ubiq38.eem.corp.google.com [172.25.129.80])
by hpza9.eem.corp.google.com (Postfix) with ESMTP id BDDA25C0050
for <ganeti-devel@googlegroups.com>; Tue, 23 Oct 2012 06:54:32 -0700 (PDT)
Received: by hpgntaa-ubiq38.eem.corp.google.com (Postfix, from userid 55155)
id 820F3A3992; Tue, 23 Oct 2012 15:54:32 +0200 (CEST)
From: Michael Hanselmann <han...@google.com>
To: ganeti-devel@googlegroups.com
Subject: =?UTF-8?q?=5BPATCH=20master=204=C2=BD/8=5D=20bdev=3A=20Add=20verification=20for=20file=20storage=20paths?=
Date: Tue, 23 Oct 2012 15:54:32 +0200
Message-Id: <d09977f3bc3104e4eff70c26fb647eeb81746b99.1351000441.git.han...@google.com>
X-Mailer: git-send-email 1.7.7.3
In-Reply-To: <CAEsTj1tnpCD_7Cp=vK+iyEO4ztA9bCFKf6Ps8HKRwn9=4Az...@mail.gmail.com>
References: <CAEsTj1tnpCD_7Cp=vK+iyEO4ztA9bCFKf6Ps8HKRwn9=4Az...@mail.gmail.com>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Gm-Message-State: ALoCoQlDqvw4LCeWupYpb/HM/eh/aNPk5HYXVCEv7uHTNthkydmsgmZOiRgjfQRvFUmjHO9oCOudOKl7Rnh+V6o0scbk567yUnpMolmemZQjRj0xATi1CVtPB0LavMstr5ezVgdcxSWN9b3/7cr0kdBXwxJr00sFzhHnQpuDkubxSDVTQXQ4C3Vn6JoyILUUK03bbTZE4HBwIUWqbrDxiJQnTo4AyrKczrZUc/bBTT/36mqznoXPvuQ=
An earlier version of this patch series verified all paths in cmdlib in
the master daemon. With this change all that verification code is moved
to bdev to run inside the node daemon. The checks are much stricter
now--it is no longer possible to use forbidden paths (e.g. /bin) to
manipulate file storage devices (once these checks are being used).
---
lib/backend.py | 1 +
lib/bdev.py | 63 ++++++++++++++++++++++++++++++++-
test/ganeti.bdev_unittest.py | 79 +++++++++++++++++++++++++++++++++++++++--
3 files changed, 137 insertions(+), 6 deletions(-)
diff --git a/lib/backend.py b/lib/backend.py
index 032547d..6da70d6 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -800,6 +800,7 @@ def VerifyNode(what, cluster_name):
result[constants.NV_BRIDGES] = [bridge
for bridge in what[constants.NV_BRIDGES]
if not utils.BridgeExists(bridge)]
+
return result
diff --git a/lib/bdev.py b/lib/bdev.py
index 8d41f53..487298d 100644
--- a/lib/bdev.py
+++ b/lib/bdev.py
@@ -29,6 +29,7 @@ import stat
import pyparsing as pyp
import os
import logging
+import itertools
from ganeti import utils
from ganeti import errors
@@ -100,6 +101,58 @@ def _CanReadDevice(path):
return False
+def _GetForbiddenFileStoragePaths():
+ """Builds a list of path prefixes which shouldn't be used for file storage.
+
+ @rtype: frozenset
+
+ """
+ paths = set([
+ "/boot",
+ "/dev",
+ "/etc",
+ "/home",
+ "/proc",
+ "/root",
+ "/sys",
+ ])
+
+ for prefix in ["", "/usr", "/usr/local"]:
+ paths.update(map(lambda s: "%s/%s" % (prefix, s),
+ ["bin", "lib", "lib32", "lib64", "sbin"]))
+
+ return frozenset(map(os.path.normpath, paths))
+
+
+def _ComputeWrongFileStoragePaths(paths,
+ _forbidden=_GetForbiddenFileStoragePaths()):
+ """Cross-checks a list of paths for prefixes considered bad.
+
+ Some paths, e.g. "/bin", should not be used for file storage.
+
+ @type paths: list
+ @param paths: List of paths to be checked
+ @rtype: list
+ @return: Sorted list of paths for which the user should be warned
+
+ """
+ def _Check(path):
+ return (not os.path.isabs(path) or
+ path in _forbidden or
+ filter(lambda p: utils.IsBelowDir(p, path), _forbidden))
+
+ return utils.NiceSort(filter(_Check, map(os.path.normpath, paths)))
+
+
+def ComputeWrongFileStoragePaths(_filename=pathutils.FILE_STORAGE_PATHS_FILE):
+ """Returns a list of file storage paths whose prefix is considered bad.
+
+ See L{_VerifyFileStoragePaths}.
+
+ """
+ return _ComputeWrongFileStoragePaths(_LoadAllowedFileStoragePaths(_filename))
+
+
def _CheckFileStoragePath(path, allowed):
"""Checks if a path is in a list of allowed paths for file storage.
@@ -126,7 +179,7 @@ def _CheckFileStoragePath(path, allowed):
" storage" % path)
-def LoadAllowedFileStoragePaths(filename):
+def _LoadAllowedFileStoragePaths(filename):
"""Loads file containing allowed file storage paths.
@rtype: list
@@ -149,7 +202,13 @@ def CheckFileStoragePath(path, _filename=pathutils.FILE_STORAGE_PATHS_FILE):
@raise errors.FileStoragePathError: If the path is not allowed
"""
- _CheckFileStoragePath(path, LoadAllowedFileStoragePaths(_filename))
+ allowed = _LoadAllowedFileStoragePaths(_filename)
+
+ if _ComputeWrongFileStoragePaths([path]):
+ raise errors.FileStoragePathError("Path '%s' uses a forbidden prefix" %
+ path)
+
+ _CheckFileStoragePath(path, allowed)
class BlockDev(object):
diff --git a/test/ganeti.bdev_unittest.py b/test/ganeti.bdev_unittest.py
index e900719..d58ee22 100755
--- a/test/ganeti.bdev_unittest.py
+++ b/test/ganeti.bdev_unittest.py
@@ -373,7 +373,48 @@ class TestRADOSBlockDevice(testutils.GanetiTestCase):
output_extra_matches, volume_name)
-class TestCheckFileStoragePath(unittest.TestCase):
+class TestComputeWrongFileStoragePathsInternal(unittest.TestCase):
+ def testPaths(self):
+ paths = bdev._GetForbiddenFileStoragePaths()
+
+ for path in ["/bin", "/usr/local/sbin", "/lib64", "/etc", "/sys"]:
+ self.assertTrue(path in paths)
+
+ self.assertEqual(set(map(os.path.normpath, paths)), paths)
+
+ def test(self):
+ vfsp = bdev._ComputeWrongFileStoragePaths
+ self.assertEqual(vfsp([]), [])
+ self.assertEqual(vfsp(["/tmp"]), [])
+ self.assertEqual(vfsp(["/bin/ls"]), ["/bin/ls"])
+ self.assertEqual(vfsp(["/bin"]), ["/bin"])
+ self.assertEqual(vfsp(["/usr/sbin/vim", "/srv/file-storage"]),
+ ["/usr/sbin/vim"])
+
+
+class TestComputeWrongFileStoragePaths(testutils.GanetiTestCase):
+ def test(self):
+ tmpfile = self._CreateTempFile()
+
+ utils.WriteFile(tmpfile, data="""
+ /tmp
+ x/y///z/relative
+ # This is a test file
+ /srv/storage
+ /bin
+ /usr/local/lib32/
+ relative/path
+ """)
+
+ self.assertEqual(bdev.ComputeWrongFileStoragePaths(_filename=tmpfile), [
+ "/bin",
+ "/usr/local/lib32",
+ "relative/path",
+ "x/y/z/relative",
+ ])
+
+
+class TestCheckFileStoragePathInternal(unittest.TestCase):
def testNonAbsolute(self):
for i in ["", "tmp", "foo/bar/baz"]:
self.assertRaises(errors.FileStoragePathError,
@@ -395,14 +436,44 @@ class TestCheckFileStoragePath(unittest.TestCase):
bdev._CheckFileStoragePath("/tmp/foo/a/x", ["/tmp/foo"])
+class TestCheckFileStoragePath(testutils.GanetiTestCase):
+ def testNonExistantFile(self):
+ filename = "/tmp/this/file/does/not/exist"
+ assert not os.path.exists(filename)
+ self.assertRaises(errors.FileStoragePathError,
+ bdev.CheckFileStoragePath, "/bin/", _filename=filename)
+ self.assertRaises(errors.FileStoragePathError,
+ bdev.CheckFileStoragePath, "/srv/file-storage",
+ _filename=filename)
+
+ def testAllowedPath(self):
+ tmpfile = self._CreateTempFile()
+
+ utils.WriteFile(tmpfile, data="""
+ /srv/storage
+ """)
+
+ bdev.CheckFileStoragePath("/srv/storage/inst1", _filename=tmpfile)
+
+ # No additional path component
+ self.assertRaises(errors.FileStoragePathError,
+ bdev.CheckFileStoragePath, "/srv/storage",
+ _filename=tmpfile)
+
+ # Forbidden path
+ self.assertRaises(errors.FileStoragePathError,
+ bdev.CheckFileStoragePath, "/usr/lib64/xyz",
+ _filename=tmpfile)
+
+
class TestLoadAllowedFileStoragePaths(testutils.GanetiTestCase):
def testDevNull(self):
- self.assertEqual(bdev.LoadAllowedFileStoragePaths("/dev/null"), [])
+ self.assertEqual(bdev._LoadAllowedFileStoragePaths("/dev/null"), [])
def testNonExistantFile(self):
filename = "/tmp/this/file/does/not/exist"
assert not os.path.exists(filename)
- self.assertEqual(bdev.LoadAllowedFileStoragePaths(filename), [])
+ self.assertEqual(bdev._LoadAllowedFileStoragePaths(filename), [])
def test(self):
tmpfile = self._CreateTempFile()
@@ -414,7 +485,7 @@ class TestLoadAllowedFileStoragePaths(testutils.GanetiTestCase):
relative/path
""")
- self.assertEqual(bdev.LoadAllowedFileStoragePaths(tmpfile), [
+ self.assertEqual(bdev._LoadAllowedFileStoragePaths(tmpfile), [
"/tmp",
"/srv/storage",
"relative/path",
--
1.7.7.3