-
Notifications
You must be signed in to change notification settings - Fork 202
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
password: Prevent password logging and persisting
Vdsm should not expose passwords in log messages. We have code in HSM, protecting some methods that accept connection lists, which may include a password parameter. However this is not the right place to protect passwords, as they are logged now by the jsonrpc server. This patch introduces the password library, providing: ProtectedPassword class for wrapping password value, so it is not logged or persisted by mistake. protect_passwords replace passwords value with ProtectedPassword object in nested structures. unprotect_passwords replace ProtectedPassword objects with the actual password value. The jsonprc server protect passwords in request parameters, and unprotect passwords in response result. This is safest and simplest way, as we cannot forget to protect a method. The xmlrpc server handle protecting and unprotecting in the method level, since this is the only place where we can detect a password parameter. Passwords read from iscsi session and libvirt password file are also protected when they enter the application. Code that needs to access protected password value must access the password.value attribute. Note that xmlrpc server has incomplete and fragile password protection for non-irs methods (see wrapApiMethod). Fixing this needs more work. Change-Id: Icc849ad8bdc1b1fd09884e18038427d96e66110f Bug-Url: https://bugzilla.redhat.com/1220039 Signed-off-by: Nir Soffer <nsoffer@redhat.com> Reviewed-on: https://gerrit.ovirt.org/40707 Reviewed-by: Piotr Kliczewski <piotr.kliczewski@gmail.com> Reviewed-by: Francesco Romani <fromani@redhat.com> Reviewed-by: Dan Kenigsberg <danken@redhat.com> Continuous-Integration: Jenkins CI
- Loading branch information
1 parent
1b4c25c
commit 862d644
Showing
18 changed files
with
354 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# | ||
# Copyright 2015 Red Hat, Inc. | ||
# | ||
# This program is free software; you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation; either version 2 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program; if not, write to the Free Software | ||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
# | ||
# Refer to the README and COPYING files for full details of the license | ||
# | ||
|
||
|
||
class ProtectedPassword(object): | ||
""" | ||
Protect a password so it will not be logged or serialized by mistake. | ||
""" | ||
def __init__(self, value): | ||
self.value = value | ||
|
||
def __eq__(self, other): | ||
return type(self) == type(other) and self.value == other.value | ||
|
||
def __ne__(self, other): | ||
return not self.__eq__(other) | ||
|
||
def __getstate__(self): | ||
""" Do not serialize the the real password """ | ||
raise TypeError("ProtectedPassword object cannot be pickled") | ||
|
||
def __str__(self): | ||
return "********" | ||
|
||
def __repr__(self): | ||
return repr(str(self)) | ||
|
||
|
||
def protect_passwords(obj): | ||
""" | ||
Replace "password" values with ProtectedPassword() object. | ||
Accept a dict, list of dicts or nested structure containing these types. | ||
""" | ||
for d, key, value in _walk(obj): | ||
d[key] = ProtectedPassword(value) | ||
return obj | ||
|
||
|
||
def unprotect_passwords(obj): | ||
""" | ||
Replace ProtectedPassword() objects with the actual password value. | ||
Accept a dict, list of dicts or nested structure containing these types. | ||
""" | ||
for d, key, value in _walk(obj): | ||
if isinstance(value, ProtectedPassword): | ||
d[key] = value.value | ||
return obj | ||
|
||
|
||
def _walk(obj): | ||
if isinstance(obj, dict): | ||
for key, value in obj.iteritems(): | ||
if key == "password": | ||
yield obj, key, value | ||
elif isinstance(value, (dict, list)): | ||
for d, k, v in _walk(value): | ||
yield d, k, v | ||
elif isinstance(obj, list): | ||
for item in obj: | ||
for d, k, v in _walk(item): | ||
yield d, k, v |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
# | ||
# Copyright 2015 Red Hat, Inc. | ||
# | ||
# This program is free software; you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation; either version 2 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program; if not, write to the Free Software | ||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
# | ||
# Refer to the README and COPYING files for full details of the license | ||
# | ||
|
||
import marshal | ||
from testlib import VdsmTestCase | ||
from testlib import expandPermutations, permutations | ||
from vdsm.compat import pickle, json | ||
from vdsm.password import (ProtectedPassword, | ||
protect_passwords, | ||
unprotect_passwords) | ||
|
||
|
||
class ProtectedPasswordTests(VdsmTestCase): | ||
|
||
def test_str(self): | ||
p = ProtectedPassword("12345678") | ||
self.assertNotIn("12345678", str(p)) | ||
|
||
def test_repr(self): | ||
p = ProtectedPassword("12345678") | ||
self.assertNotIn("12345678", repr(p)) | ||
|
||
def test_value(self): | ||
p = ProtectedPassword("12345678") | ||
self.assertEqual("12345678", p.value) | ||
|
||
def test_eq(self): | ||
p1 = ProtectedPassword("12345678") | ||
p2 = ProtectedPassword("12345678") | ||
self.assertEqual(p1, p2) | ||
|
||
def test_ne(self): | ||
p1 = ProtectedPassword("12345678") | ||
p2 = ProtectedPassword("12345678") | ||
self.assertFalse(p1 != p2) | ||
|
||
def test_no_pickle(self): | ||
p1 = ProtectedPassword("12345678") | ||
self.assertRaises(TypeError, pickle.dumps, p1) | ||
|
||
def test_no_marshal(self): | ||
p1 = ProtectedPassword("12345678") | ||
self.assertRaises(ValueError, marshal.dumps, p1) | ||
|
||
def test_no_json(self): | ||
p1 = ProtectedPassword("12345678") | ||
self.assertRaises(TypeError, json.dumps, p1) | ||
|
||
|
||
@expandPermutations | ||
class ProtectTests(VdsmTestCase): | ||
|
||
@permutations([[list()], [dict()], [tuple()]]) | ||
def test_protect_empty(self, params): | ||
self.assertEqual(params, protect_passwords(params)) | ||
|
||
@permutations([[list()], [dict()], [tuple()]]) | ||
def test_unprotect_empty(self, result): | ||
self.assertEqual(result, unprotect_passwords(result)) | ||
|
||
def test_protect_dict(self): | ||
unprotected = dict_unprotedted() | ||
protected = dict_protected() | ||
self.assertEqual(protected, protect_passwords(unprotected)) | ||
|
||
def test_unprotect_dict(self): | ||
protected = dict_protected() | ||
unprotected = dict_unprotedted() | ||
self.assertEqual(unprotected, unprotect_passwords(protected)) | ||
|
||
def test_protect_nested_dicts(self): | ||
unprotected = nested_dicts_unprotected() | ||
protected = nested_dicts_protected() | ||
self.assertEqual(protected, protect_passwords(unprotected)) | ||
|
||
def test_unprotect_nested_dicts(self): | ||
protected = nested_dicts_protected() | ||
unprotected = nested_dicts_unprotected() | ||
self.assertEqual(unprotected, unprotect_passwords(protected)) | ||
|
||
def test_protect_lists_of_dicts(self): | ||
unprotected = lists_of_dicts_unprotected() | ||
protected = lists_of_dicts_protected() | ||
self.assertEqual(protected, protect_passwords(unprotected)) | ||
|
||
def test_unprotect_lists_of_dicts(self): | ||
protected = lists_of_dicts_protected() | ||
unprotected = lists_of_dicts_unprotected() | ||
self.assertEqual(unprotected, unprotect_passwords(protected)) | ||
|
||
def test_protect_nested_lists_of_dicts(self): | ||
unprotected = nested_lists_of_dicts_unprotected() | ||
protected = nested_lists_of_dicts_protected() | ||
self.assertEqual(protected, protect_passwords(unprotected)) | ||
|
||
def test_unprotect_nested_lists_of_dicts(self): | ||
protected = nested_lists_of_dicts_protected() | ||
unprotected = nested_lists_of_dicts_unprotected() | ||
self.assertEqual(unprotected, unprotect_passwords(protected)) | ||
|
||
|
||
def dict_unprotedted(): | ||
return { | ||
"key": "value", | ||
"password": "12345678" | ||
} | ||
|
||
|
||
def dict_protected(): | ||
return { | ||
"key": "value", | ||
"password": ProtectedPassword("12345678") | ||
} | ||
|
||
|
||
def nested_dicts_unprotected(): | ||
return { | ||
"key": "value", | ||
"nested": { | ||
"password": "12345678", | ||
"nested": { | ||
"key": "value", | ||
"password": "87654321", | ||
} | ||
} | ||
} | ||
|
||
|
||
def nested_dicts_protected(): | ||
return { | ||
"key": "value", | ||
"nested": { | ||
"password": ProtectedPassword("12345678"), | ||
"nested": { | ||
"key": "value", | ||
"password": ProtectedPassword("87654321"), | ||
} | ||
} | ||
} | ||
|
||
|
||
def lists_of_dicts_unprotected(): | ||
return [ | ||
{ | ||
"key": "value", | ||
"password": "12345678", | ||
}, | ||
{ | ||
"key": "value", | ||
"password": "87654321", | ||
} | ||
] | ||
|
||
|
||
def lists_of_dicts_protected(): | ||
return [ | ||
{ | ||
"key": "value", | ||
"password": ProtectedPassword("12345678"), | ||
}, | ||
{ | ||
"key": "value", | ||
"password": ProtectedPassword("87654321"), | ||
} | ||
] | ||
|
||
|
||
def nested_lists_of_dicts_unprotected(): | ||
return { | ||
"key": "value", | ||
"nested": [ | ||
{ | ||
"key": "value", | ||
"nested": [ | ||
{ | ||
"key": "value", | ||
"password": "12345678", | ||
} | ||
] | ||
} | ||
] | ||
} | ||
|
||
|
||
def nested_lists_of_dicts_protected(): | ||
return { | ||
"key": "value", | ||
"nested": [ | ||
{ | ||
"key": "value", | ||
"nested": [ | ||
{ | ||
"key": "value", | ||
"password": ProtectedPassword("12345678"), | ||
} | ||
] | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.