Revision f21bb4b7
b/Makefile.am | ||
---|---|---|
214 | 214 |
utils_PYTHON = \ |
215 | 215 |
lib/utils/__init__.py \ |
216 | 216 |
lib/utils/algo.py \ |
217 |
lib/utils/hash.py \ |
|
217 | 218 |
lib/utils/log.py \ |
218 | 219 |
lib/utils/mlock.py \ |
219 | 220 |
lib/utils/retry.py \ |
... | ... | |
484 | 485 |
test/ganeti.ssh_unittest.py \ |
485 | 486 |
test/ganeti.uidpool_unittest.py \ |
486 | 487 |
test/ganeti.utils.algo_unittest.py \ |
488 |
test/ganeti.utils.hash_unittest.py \ |
|
487 | 489 |
test/ganeti.utils.mlock_unittest.py \ |
488 | 490 |
test/ganeti.utils.retry_unittest.py \ |
489 | 491 |
test/ganeti.utils.text_unittest.py \ |
b/lib/utils/__init__.py | ||
---|---|---|
46 | 46 |
import OpenSSL |
47 | 47 |
import datetime |
48 | 48 |
import calendar |
49 |
import hmac |
|
50 | 49 |
|
51 | 50 |
from cStringIO import StringIO |
52 | 51 |
|
... | ... | |
59 | 58 |
from ganeti.utils.text import * # pylint: disable-msg=W0401 |
60 | 59 |
from ganeti.utils.mlock import * # pylint: disable-msg=W0401 |
61 | 60 |
from ganeti.utils.log import * # pylint: disable-msg=W0401 |
61 |
from ganeti.utils.hash import * # pylint: disable-msg=W0401 |
|
62 | 62 |
|
63 | 63 |
_locksheld = [] |
64 | 64 |
|
... | ... | |
850 | 850 |
" '_once_lock' and '_name_sequence' attributes") |
851 | 851 |
|
852 | 852 |
|
853 |
def _FingerprintFile(filename): |
|
854 |
"""Compute the fingerprint of a file. |
|
855 |
|
|
856 |
If the file does not exist, a None will be returned |
|
857 |
instead. |
|
858 |
|
|
859 |
@type filename: str |
|
860 |
@param filename: the filename to checksum |
|
861 |
@rtype: str |
|
862 |
@return: the hex digest of the sha checksum of the contents |
|
863 |
of the file |
|
864 |
|
|
865 |
""" |
|
866 |
if not (os.path.exists(filename) and os.path.isfile(filename)): |
|
867 |
return None |
|
868 |
|
|
869 |
f = open(filename) |
|
870 |
|
|
871 |
fp = compat.sha1_hash() |
|
872 |
while True: |
|
873 |
data = f.read(4096) |
|
874 |
if not data: |
|
875 |
break |
|
876 |
|
|
877 |
fp.update(data) |
|
878 |
|
|
879 |
return fp.hexdigest() |
|
880 |
|
|
881 |
|
|
882 |
def FingerprintFiles(files): |
|
883 |
"""Compute fingerprints for a list of files. |
|
884 |
|
|
885 |
@type files: list |
|
886 |
@param files: the list of filename to fingerprint |
|
887 |
@rtype: dict |
|
888 |
@return: a dictionary filename: fingerprint, holding only |
|
889 |
existing files |
|
890 |
|
|
891 |
""" |
|
892 |
ret = {} |
|
893 |
|
|
894 |
for filename in files: |
|
895 |
cksum = _FingerprintFile(filename) |
|
896 |
if cksum: |
|
897 |
ret[filename] = cksum |
|
898 |
|
|
899 |
return ret |
|
900 |
|
|
901 |
|
|
902 | 853 |
def ForceDictType(target, key_types, allowed_values=None): |
903 | 854 |
"""Force the values of a dict to have certain types. |
904 | 855 |
|
... | ... | |
2541 | 2492 |
return (cert, salt) |
2542 | 2493 |
|
2543 | 2494 |
|
2544 |
def Sha1Hmac(key, text, salt=None): |
|
2545 |
"""Calculates the HMAC-SHA1 digest of a text. |
|
2546 |
|
|
2547 |
HMAC is defined in RFC2104. |
|
2548 |
|
|
2549 |
@type key: string |
|
2550 |
@param key: Secret key |
|
2551 |
@type text: string |
|
2552 |
|
|
2553 |
""" |
|
2554 |
if salt: |
|
2555 |
salted_text = salt + text |
|
2556 |
else: |
|
2557 |
salted_text = text |
|
2558 |
|
|
2559 |
return hmac.new(key, salted_text, compat.sha1).hexdigest() |
|
2560 |
|
|
2561 |
|
|
2562 |
def VerifySha1Hmac(key, text, digest, salt=None): |
|
2563 |
"""Verifies the HMAC-SHA1 digest of a text. |
|
2564 |
|
|
2565 |
HMAC is defined in RFC2104. |
|
2566 |
|
|
2567 |
@type key: string |
|
2568 |
@param key: Secret key |
|
2569 |
@type text: string |
|
2570 |
@type digest: string |
|
2571 |
@param digest: Expected digest |
|
2572 |
@rtype: bool |
|
2573 |
@return: Whether HMAC-SHA1 digest matches |
|
2574 |
|
|
2575 |
""" |
|
2576 |
return digest.lower() == Sha1Hmac(key, text, salt=salt).lower() |
|
2577 |
|
|
2578 |
|
|
2579 | 2495 |
def FindMatch(data, name): |
2580 | 2496 |
"""Tries to find an item in a dictionary matching a name. |
2581 | 2497 |
|
b/lib/utils/hash.py | ||
---|---|---|
1 |
# |
|
2 |
# |
|
3 |
|
|
4 |
# Copyright (C) 2006, 2007, 2010, 2011 Google Inc. |
|
5 |
# |
|
6 |
# This program is free software; you can redistribute it and/or modify |
|
7 |
# it under the terms of the GNU General Public License as published by |
|
8 |
# the Free Software Foundation; either version 2 of the License, or |
|
9 |
# (at your option) any later version. |
|
10 |
# |
|
11 |
# This program is distributed in the hope that it will be useful, but |
|
12 |
# WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
14 |
# General Public License for more details. |
|
15 |
# |
|
16 |
# You should have received a copy of the GNU General Public License |
|
17 |
# along with this program; if not, write to the Free Software |
|
18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
19 |
# 02110-1301, USA. |
|
20 |
|
|
21 |
"""Utility functions for hashing. |
|
22 |
|
|
23 |
""" |
|
24 |
|
|
25 |
import os |
|
26 |
import hmac |
|
27 |
|
|
28 |
from ganeti import compat |
|
29 |
|
|
30 |
|
|
31 |
def Sha1Hmac(key, text, salt=None): |
|
32 |
"""Calculates the HMAC-SHA1 digest of a text. |
|
33 |
|
|
34 |
HMAC is defined in RFC2104. |
|
35 |
|
|
36 |
@type key: string |
|
37 |
@param key: Secret key |
|
38 |
@type text: string |
|
39 |
|
|
40 |
""" |
|
41 |
if salt: |
|
42 |
salted_text = salt + text |
|
43 |
else: |
|
44 |
salted_text = text |
|
45 |
|
|
46 |
return hmac.new(key, salted_text, compat.sha1).hexdigest() |
|
47 |
|
|
48 |
|
|
49 |
def VerifySha1Hmac(key, text, digest, salt=None): |
|
50 |
"""Verifies the HMAC-SHA1 digest of a text. |
|
51 |
|
|
52 |
HMAC is defined in RFC2104. |
|
53 |
|
|
54 |
@type key: string |
|
55 |
@param key: Secret key |
|
56 |
@type text: string |
|
57 |
@type digest: string |
|
58 |
@param digest: Expected digest |
|
59 |
@rtype: bool |
|
60 |
@return: Whether HMAC-SHA1 digest matches |
|
61 |
|
|
62 |
""" |
|
63 |
return digest.lower() == Sha1Hmac(key, text, salt=salt).lower() |
|
64 |
|
|
65 |
|
|
66 |
def _FingerprintFile(filename): |
|
67 |
"""Compute the fingerprint of a file. |
|
68 |
|
|
69 |
If the file does not exist, a None will be returned |
|
70 |
instead. |
|
71 |
|
|
72 |
@type filename: str |
|
73 |
@param filename: the filename to checksum |
|
74 |
@rtype: str |
|
75 |
@return: the hex digest of the sha checksum of the contents |
|
76 |
of the file |
|
77 |
|
|
78 |
""" |
|
79 |
if not (os.path.exists(filename) and os.path.isfile(filename)): |
|
80 |
return None |
|
81 |
|
|
82 |
f = open(filename) |
|
83 |
|
|
84 |
fp = compat.sha1_hash() |
|
85 |
while True: |
|
86 |
data = f.read(4096) |
|
87 |
if not data: |
|
88 |
break |
|
89 |
|
|
90 |
fp.update(data) |
|
91 |
|
|
92 |
return fp.hexdigest() |
|
93 |
|
|
94 |
|
|
95 |
def FingerprintFiles(files): |
|
96 |
"""Compute fingerprints for a list of files. |
|
97 |
|
|
98 |
@type files: list |
|
99 |
@param files: the list of filename to fingerprint |
|
100 |
@rtype: dict |
|
101 |
@return: a dictionary filename: fingerprint, holding only |
|
102 |
existing files |
|
103 |
|
|
104 |
""" |
|
105 |
ret = {} |
|
106 |
|
|
107 |
for filename in files: |
|
108 |
cksum = _FingerprintFile(filename) |
|
109 |
if cksum: |
|
110 |
ret[filename] = cksum |
|
111 |
|
|
112 |
return ret |
b/test/ganeti.utils.hash_unittest.py | ||
---|---|---|
1 |
#!/usr/bin/python |
|
2 |
# |
|
3 |
|
|
4 |
# Copyright (C) 2006, 2007, 2010, 2011 Google Inc. |
|
5 |
# |
|
6 |
# This program is free software; you can redistribute it and/or modify |
|
7 |
# it under the terms of the GNU General Public License as published by |
|
8 |
# the Free Software Foundation; either version 2 of the License, or |
|
9 |
# (at your option) any later version. |
|
10 |
# |
|
11 |
# This program is distributed in the hope that it will be useful, but |
|
12 |
# WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
14 |
# General Public License for more details. |
|
15 |
# |
|
16 |
# You should have received a copy of the GNU General Public License |
|
17 |
# along with this program; if not, write to the Free Software |
|
18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
19 |
# 02110-1301, USA. |
|
20 |
|
|
21 |
|
|
22 |
"""Script for testing ganeti.utils.hash""" |
|
23 |
|
|
24 |
import unittest |
|
25 |
import random |
|
26 |
import operator |
|
27 |
import tempfile |
|
28 |
|
|
29 |
from ganeti import constants |
|
30 |
from ganeti import utils |
|
31 |
|
|
32 |
import testutils |
|
33 |
|
|
34 |
|
|
35 |
class TestHmacFunctions(unittest.TestCase): |
|
36 |
# Digests can be checked with "openssl sha1 -hmac $key" |
|
37 |
def testSha1Hmac(self): |
|
38 |
self.assertEqual(utils.Sha1Hmac("", ""), |
|
39 |
"fbdb1d1b18aa6c08324b7d64b71fb76370690e1d") |
|
40 |
self.assertEqual(utils.Sha1Hmac("3YzMxZWE", "Hello World"), |
|
41 |
"ef4f3bda82212ecb2f7ce868888a19092481f1fd") |
|
42 |
self.assertEqual(utils.Sha1Hmac("TguMTA2K", ""), |
|
43 |
"f904c2476527c6d3e6609ab683c66fa0652cb1dc") |
|
44 |
|
|
45 |
longtext = 1500 * "The quick brown fox jumps over the lazy dog\n" |
|
46 |
self.assertEqual(utils.Sha1Hmac("3YzMxZWE", longtext), |
|
47 |
"35901b9a3001a7cdcf8e0e9d7c2e79df2223af54") |
|
48 |
|
|
49 |
def testSha1HmacSalt(self): |
|
50 |
self.assertEqual(utils.Sha1Hmac("TguMTA2K", "", salt="abc0"), |
|
51 |
"4999bf342470eadb11dfcd24ca5680cf9fd7cdce") |
|
52 |
self.assertEqual(utils.Sha1Hmac("TguMTA2K", "", salt="abc9"), |
|
53 |
"17a4adc34d69c0d367d4ffbef96fd41d4df7a6e8") |
|
54 |
self.assertEqual(utils.Sha1Hmac("3YzMxZWE", "Hello World", salt="xyz0"), |
|
55 |
"7f264f8114c9066afc9bb7636e1786d996d3cc0d") |
|
56 |
|
|
57 |
def testVerifySha1Hmac(self): |
|
58 |
self.assert_(utils.VerifySha1Hmac("", "", ("fbdb1d1b18aa6c08324b" |
|
59 |
"7d64b71fb76370690e1d"))) |
|
60 |
self.assert_(utils.VerifySha1Hmac("TguMTA2K", "", |
|
61 |
("f904c2476527c6d3e660" |
|
62 |
"9ab683c66fa0652cb1dc"))) |
|
63 |
|
|
64 |
digest = "ef4f3bda82212ecb2f7ce868888a19092481f1fd" |
|
65 |
self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", digest)) |
|
66 |
self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", |
|
67 |
digest.lower())) |
|
68 |
self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", |
|
69 |
digest.upper())) |
|
70 |
self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", |
|
71 |
digest.title())) |
|
72 |
|
|
73 |
def testVerifySha1HmacSalt(self): |
|
74 |
self.assert_(utils.VerifySha1Hmac("TguMTA2K", "", |
|
75 |
("17a4adc34d69c0d367d4" |
|
76 |
"ffbef96fd41d4df7a6e8"), |
|
77 |
salt="abc9")) |
|
78 |
self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", |
|
79 |
("7f264f8114c9066afc9b" |
|
80 |
"b7636e1786d996d3cc0d"), |
|
81 |
salt="xyz0")) |
|
82 |
|
|
83 |
|
|
84 |
class TestFingerprintFiles(unittest.TestCase): |
|
85 |
def setUp(self): |
|
86 |
self.tmpfile = tempfile.NamedTemporaryFile() |
|
87 |
self.tmpfile2 = tempfile.NamedTemporaryFile() |
|
88 |
utils.WriteFile(self.tmpfile2.name, data="Hello World\n") |
|
89 |
self.results = { |
|
90 |
self.tmpfile.name: "da39a3ee5e6b4b0d3255bfef95601890afd80709", |
|
91 |
self.tmpfile2.name: "648a6a6ffffdaa0badb23b8baf90b6168dd16b3a", |
|
92 |
} |
|
93 |
|
|
94 |
def testSingleFile(self): |
|
95 |
self.assertEqual(utils.hash._FingerprintFile(self.tmpfile.name), |
|
96 |
self.results[self.tmpfile.name]) |
|
97 |
|
|
98 |
self.assertEqual(utils.hash._FingerprintFile("/no/such/file"), None) |
|
99 |
|
|
100 |
def testBigFile(self): |
|
101 |
self.tmpfile.write("A" * 8192) |
|
102 |
self.tmpfile.flush() |
|
103 |
self.assertEqual(utils.hash._FingerprintFile(self.tmpfile.name), |
|
104 |
"35b6795ca20d6dc0aff8c7c110c96cd1070b8c38") |
|
105 |
|
|
106 |
def testMultiple(self): |
|
107 |
all_files = self.results.keys() |
|
108 |
all_files.append("/no/such/file") |
|
109 |
self.assertEqual(utils.FingerprintFiles(self.results.keys()), self.results) |
|
110 |
|
|
111 |
|
|
112 |
if __name__ == "__main__": |
|
113 |
testutils.GanetiTestProgram() |
b/test/ganeti.utils_unittest.py | ||
---|---|---|
1471 | 1471 |
utils.RunInSeparateProcess, _exc) |
1472 | 1472 |
|
1473 | 1473 |
|
1474 |
class TestFingerprintFiles(unittest.TestCase): |
|
1475 |
def setUp(self): |
|
1476 |
self.tmpfile = tempfile.NamedTemporaryFile() |
|
1477 |
self.tmpfile2 = tempfile.NamedTemporaryFile() |
|
1478 |
utils.WriteFile(self.tmpfile2.name, data="Hello World\n") |
|
1479 |
self.results = { |
|
1480 |
self.tmpfile.name: "da39a3ee5e6b4b0d3255bfef95601890afd80709", |
|
1481 |
self.tmpfile2.name: "648a6a6ffffdaa0badb23b8baf90b6168dd16b3a", |
|
1482 |
} |
|
1483 |
|
|
1484 |
def testSingleFile(self): |
|
1485 |
self.assertEqual(utils._FingerprintFile(self.tmpfile.name), |
|
1486 |
self.results[self.tmpfile.name]) |
|
1487 |
|
|
1488 |
self.assertEqual(utils._FingerprintFile("/no/such/file"), None) |
|
1489 |
|
|
1490 |
def testBigFile(self): |
|
1491 |
self.tmpfile.write("A" * 8192) |
|
1492 |
self.tmpfile.flush() |
|
1493 |
self.assertEqual(utils._FingerprintFile(self.tmpfile.name), |
|
1494 |
"35b6795ca20d6dc0aff8c7c110c96cd1070b8c38") |
|
1495 |
|
|
1496 |
def testMultiple(self): |
|
1497 |
all_files = self.results.keys() |
|
1498 |
all_files.append("/no/such/file") |
|
1499 |
self.assertEqual(utils.FingerprintFiles(self.results.keys()), self.results) |
|
1500 |
|
|
1501 |
|
|
1502 | 1474 |
class TestGenerateSelfSignedX509Cert(unittest.TestCase): |
1503 | 1475 |
def setUp(self): |
1504 | 1476 |
self.tmpdir = tempfile.mkdtemp() |
... | ... | |
1829 | 1801 |
self.assertEqual(errcode, utils.CERT_ERROR) |
1830 | 1802 |
|
1831 | 1803 |
|
1832 |
class TestHmacFunctions(unittest.TestCase): |
|
1833 |
# Digests can be checked with "openssl sha1 -hmac $key" |
|
1834 |
def testSha1Hmac(self): |
|
1835 |
self.assertEqual(utils.Sha1Hmac("", ""), |
|
1836 |
"fbdb1d1b18aa6c08324b7d64b71fb76370690e1d") |
|
1837 |
self.assertEqual(utils.Sha1Hmac("3YzMxZWE", "Hello World"), |
|
1838 |
"ef4f3bda82212ecb2f7ce868888a19092481f1fd") |
|
1839 |
self.assertEqual(utils.Sha1Hmac("TguMTA2K", ""), |
|
1840 |
"f904c2476527c6d3e6609ab683c66fa0652cb1dc") |
|
1841 |
|
|
1842 |
longtext = 1500 * "The quick brown fox jumps over the lazy dog\n" |
|
1843 |
self.assertEqual(utils.Sha1Hmac("3YzMxZWE", longtext), |
|
1844 |
"35901b9a3001a7cdcf8e0e9d7c2e79df2223af54") |
|
1845 |
|
|
1846 |
def testSha1HmacSalt(self): |
|
1847 |
self.assertEqual(utils.Sha1Hmac("TguMTA2K", "", salt="abc0"), |
|
1848 |
"4999bf342470eadb11dfcd24ca5680cf9fd7cdce") |
|
1849 |
self.assertEqual(utils.Sha1Hmac("TguMTA2K", "", salt="abc9"), |
|
1850 |
"17a4adc34d69c0d367d4ffbef96fd41d4df7a6e8") |
|
1851 |
self.assertEqual(utils.Sha1Hmac("3YzMxZWE", "Hello World", salt="xyz0"), |
|
1852 |
"7f264f8114c9066afc9bb7636e1786d996d3cc0d") |
|
1853 |
|
|
1854 |
def testVerifySha1Hmac(self): |
|
1855 |
self.assert_(utils.VerifySha1Hmac("", "", ("fbdb1d1b18aa6c08324b" |
|
1856 |
"7d64b71fb76370690e1d"))) |
|
1857 |
self.assert_(utils.VerifySha1Hmac("TguMTA2K", "", |
|
1858 |
("f904c2476527c6d3e660" |
|
1859 |
"9ab683c66fa0652cb1dc"))) |
|
1860 |
|
|
1861 |
digest = "ef4f3bda82212ecb2f7ce868888a19092481f1fd" |
|
1862 |
self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", digest)) |
|
1863 |
self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", |
|
1864 |
digest.lower())) |
|
1865 |
self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", |
|
1866 |
digest.upper())) |
|
1867 |
self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", |
|
1868 |
digest.title())) |
|
1869 |
|
|
1870 |
def testVerifySha1HmacSalt(self): |
|
1871 |
self.assert_(utils.VerifySha1Hmac("TguMTA2K", "", |
|
1872 |
("17a4adc34d69c0d367d4" |
|
1873 |
"ffbef96fd41d4df7a6e8"), |
|
1874 |
salt="abc9")) |
|
1875 |
self.assert_(utils.VerifySha1Hmac("3YzMxZWE", "Hello World", |
|
1876 |
("7f264f8114c9066afc9b" |
|
1877 |
"b7636e1786d996d3cc0d"), |
|
1878 |
salt="xyz0")) |
|
1879 |
|
|
1880 |
|
|
1881 | 1804 |
class TestIgnoreSignals(unittest.TestCase): |
1882 | 1805 |
"""Test the IgnoreSignals decorator""" |
1883 | 1806 |
|
Also available in: Unified diff