Do not leak credentials on leader-set failure
This will also give us more insights into the leader-set failure happening in the linked bug. Also updated project files from latest release-tools templates. Also blacklisted libjuju 2.8.3 which causes spurious JujuAPIError's. Change-Id: I51b890098df6d918c1d84adba272559ef45411bb Partial-Bug: #1890256
This commit is contained in:
parent
f72ae6160b
commit
f9aa92c7ce
@ -1462,7 +1462,7 @@ def set_admin_passwd(passwd, user=None):
|
|||||||
if user is None:
|
if user is None:
|
||||||
user = config('admin-user')
|
user = config('admin-user')
|
||||||
|
|
||||||
leader_set({'{}_passwd'.format(user): passwd})
|
_leader_set_secret({'{}_passwd'.format(user): passwd})
|
||||||
|
|
||||||
|
|
||||||
def get_api_version():
|
def get_api_version():
|
||||||
@ -1620,7 +1620,7 @@ def _migrate_admin_password():
|
|||||||
if is_leader() and os.path.exists(STORED_PASSWD):
|
if is_leader() and os.path.exists(STORED_PASSWD):
|
||||||
log('Migrating on-disk stored passwords to leader storage')
|
log('Migrating on-disk stored passwords to leader storage')
|
||||||
with open(STORED_PASSWD) as fd:
|
with open(STORED_PASSWD) as fd:
|
||||||
leader_set({"admin_passwd": fd.readline().strip('\n')})
|
_leader_set_secret({"admin_passwd": fd.readline().strip('\n')})
|
||||||
|
|
||||||
os.unlink(STORED_PASSWD)
|
os.unlink(STORED_PASSWD)
|
||||||
|
|
||||||
@ -1630,8 +1630,8 @@ def _migrate_service_passwords():
|
|||||||
if is_leader() and os.path.exists(SERVICE_PASSWD_PATH):
|
if is_leader() and os.path.exists(SERVICE_PASSWD_PATH):
|
||||||
log('Migrating on-disk stored passwords to leader storage')
|
log('Migrating on-disk stored passwords to leader storage')
|
||||||
creds = load_stored_passwords()
|
creds = load_stored_passwords()
|
||||||
for k, v in creds.items():
|
_leader_set_secret({"{}_passwd".format(k): v
|
||||||
leader_set({"{}_passwd".format(k): v})
|
for k, v in creds.items()})
|
||||||
os.unlink(SERVICE_PASSWD_PATH)
|
os.unlink(SERVICE_PASSWD_PATH)
|
||||||
|
|
||||||
|
|
||||||
@ -1645,7 +1645,7 @@ def get_service_password(service_username):
|
|||||||
|
|
||||||
|
|
||||||
def set_service_password(passwd, user):
|
def set_service_password(passwd, user):
|
||||||
leader_set({"{}_passwd".format(user): passwd})
|
_leader_set_secret({"{}_passwd".format(user): passwd})
|
||||||
|
|
||||||
|
|
||||||
def is_password_changed(username, passwd):
|
def is_password_changed(username, passwd):
|
||||||
@ -2535,7 +2535,7 @@ def key_leader_set():
|
|||||||
`CREDENTIAL_KEY_REPOSITORY` directories. Note that this function will fail
|
`CREDENTIAL_KEY_REPOSITORY` directories. Note that this function will fail
|
||||||
if it is called on the unit that is not the leader.
|
if it is called on the unit that is not the leader.
|
||||||
|
|
||||||
:raises: :class:`subprocess.CalledProcessError` if the leader_set fails.
|
:raises: :class:`RuntimeError` if the leader_set fails.
|
||||||
"""
|
"""
|
||||||
disk_keys = {}
|
disk_keys = {}
|
||||||
for key_repository in [FERNET_KEY_REPOSITORY, CREDENTIAL_KEY_REPOSITORY]:
|
for key_repository in [FERNET_KEY_REPOSITORY, CREDENTIAL_KEY_REPOSITORY]:
|
||||||
@ -2544,7 +2544,7 @@ def key_leader_set():
|
|||||||
with open(os.path.join(key_repository, key_number),
|
with open(os.path.join(key_repository, key_number),
|
||||||
'r') as f:
|
'r') as f:
|
||||||
disk_keys[key_repository][key_number] = f.read()
|
disk_keys[key_repository][key_number] = f.read()
|
||||||
leader_set({'key_repository': json.dumps(disk_keys)})
|
_leader_set_secret({'key_repository': json.dumps(disk_keys)})
|
||||||
|
|
||||||
|
|
||||||
def key_write():
|
def key_write():
|
||||||
@ -2702,3 +2702,18 @@ def endpoints_dict(settings):
|
|||||||
}
|
}
|
||||||
|
|
||||||
return endpoints
|
return endpoints
|
||||||
|
|
||||||
|
|
||||||
|
def _leader_set_secret(secret_dict):
|
||||||
|
"""Wrapper around leader_set doing its best to prevent leaking secrets."""
|
||||||
|
if not is_leader():
|
||||||
|
raise RuntimeError("This unit isn't the leader and therefore can't "
|
||||||
|
"call leader_set() with the given secrets")
|
||||||
|
try:
|
||||||
|
leader_set(secret_dict)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
# Do not log 'e' or 'e.cmd' as this would leak secrets. Raise a
|
||||||
|
# different exception to make sure that no one above will leak the
|
||||||
|
# secrets by accident.
|
||||||
|
raise RuntimeError('leader-set failed with {}: {}'.format(e.returncode,
|
||||||
|
e.output))
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
# requirements. They are intertwined. Also, Zaza itself should specify
|
# requirements. They are intertwined. Also, Zaza itself should specify
|
||||||
# all of its own requirements and if it doesn't, fix it there.
|
# all of its own requirements and if it doesn't, fix it there.
|
||||||
#
|
#
|
||||||
|
setuptools<50.0.0 # https://github.com/pypa/setuptools/commit/04e3df22df840c6bb244e9b27bc56750c44b7c85
|
||||||
pbr>=1.8.0,<1.9.0
|
pbr>=1.8.0,<1.9.0
|
||||||
simplejson>=2.2.0
|
simplejson>=2.2.0
|
||||||
netifaces>=0.10.4
|
netifaces>=0.10.4
|
||||||
|
@ -7,12 +7,14 @@
|
|||||||
# requirements. They are intertwined. Also, Zaza itself should specify
|
# requirements. They are intertwined. Also, Zaza itself should specify
|
||||||
# all of its own requirements and if it doesn't, fix it there.
|
# all of its own requirements and if it doesn't, fix it there.
|
||||||
#
|
#
|
||||||
|
setuptools<50.0.0 # https://github.com/pypa/setuptools/commit/04e3df22df840c6bb244e9b27bc56750c44b7c85
|
||||||
charm-tools>=2.4.4
|
charm-tools>=2.4.4
|
||||||
requests>=2.18.4
|
requests>=2.18.4
|
||||||
mock>=1.2
|
mock>=1.2
|
||||||
flake8>=2.2.4,<=2.4.1
|
flake8>=2.2.4
|
||||||
stestr>=2.2.0
|
stestr>=2.2.0
|
||||||
coverage>=4.5.2
|
coverage>=4.5.2
|
||||||
pyudev # for ceph-* charm unit tests (need to fix the ceph-* charm unit tests/mocking)
|
pyudev # for ceph-* charm unit tests (need to fix the ceph-* charm unit tests/mocking)
|
||||||
|
juju!=2.8.3 # this version causes spurious JujuAPIError's
|
||||||
git+https://github.com/openstack-charmers/zaza.git#egg=zaza;python_version>='3.0'
|
git+https://github.com/openstack-charmers/zaza.git#egg=zaza;python_version>='3.0'
|
||||||
git+https://github.com/openstack-charmers/zaza-openstack-tests.git#egg=zaza.openstack
|
git+https://github.com/openstack-charmers/zaza-openstack-tests.git#egg=zaza.openstack
|
||||||
|
2
tox.ini
2
tox.ini
@ -116,5 +116,5 @@ commands =
|
|||||||
functest-run-suite --keep-model --bundle {posargs}
|
functest-run-suite --keep-model --bundle {posargs}
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
ignore = E402,E226
|
ignore = E402,E226,W503,W504
|
||||||
exclude = */charmhelpers
|
exclude = */charmhelpers
|
||||||
|
@ -367,6 +367,7 @@ class TestKeystoneUtils(CharmTestCase):
|
|||||||
**relation_data)
|
**relation_data)
|
||||||
|
|
||||||
@patch.object(utils, 'get_real_role_names')
|
@patch.object(utils, 'get_real_role_names')
|
||||||
|
@patch.object(utils, 'is_leader')
|
||||||
@patch.object(utils, 'leader_set')
|
@patch.object(utils, 'leader_set')
|
||||||
@patch.object(utils, 'leader_get')
|
@patch.object(utils, 'leader_get')
|
||||||
@patch.object(utils, 'get_api_version')
|
@patch.object(utils, 'get_api_version')
|
||||||
@ -378,10 +379,11 @@ class TestKeystoneUtils(CharmTestCase):
|
|||||||
def test_add_service_to_keystone_no_clustered_no_https_complete_values(
|
def test_add_service_to_keystone_no_clustered_no_https_complete_values(
|
||||||
self, KeystoneManager, add_endpoint, ensure_valid_service,
|
self, KeystoneManager, add_endpoint, ensure_valid_service,
|
||||||
_resolve_address, create_user, get_api_version, leader_get,
|
_resolve_address, create_user, get_api_version, leader_get,
|
||||||
leader_set, _get_real_role_names, test_api_version=2):
|
leader_set, is_leader, _get_real_role_names, test_api_version=2):
|
||||||
_get_real_role_names.return_value = ['Member', 'SpecialRole']
|
_get_real_role_names.return_value = ['Member', 'SpecialRole']
|
||||||
get_api_version.return_value = test_api_version
|
get_api_version.return_value = test_api_version
|
||||||
leader_get.return_value = None
|
leader_get.return_value = None
|
||||||
|
is_leader.return_value = True
|
||||||
relation_id = 'identity-service:0'
|
relation_id = 'identity-service:0'
|
||||||
remote_unit = 'unit/0'
|
remote_unit = 'unit/0'
|
||||||
self.get_service_password.return_value = 'password'
|
self.get_service_password.return_value = 'password'
|
||||||
@ -1099,19 +1101,25 @@ class TestKeystoneUtils(CharmTestCase):
|
|||||||
self.assertEqual(utils.get_admin_passwd(), 'admin')
|
self.assertEqual(utils.get_admin_passwd(), 'admin')
|
||||||
leader_get.assert_called_with('test_passwd')
|
leader_get.assert_called_with('test_passwd')
|
||||||
|
|
||||||
|
@patch.object(utils, 'is_leader')
|
||||||
@patch.object(utils, 'leader_set')
|
@patch.object(utils, 'leader_set')
|
||||||
def test_set_admin_password(self, leader_set):
|
def test_set_admin_password(self, leader_set, is_leader):
|
||||||
|
is_leader.return_value = True
|
||||||
utils.set_admin_passwd('secret')
|
utils.set_admin_passwd('secret')
|
||||||
leader_set.assert_called_once_with({'admin_passwd': 'secret'})
|
leader_set.assert_called_once_with({'admin_passwd': 'secret'})
|
||||||
|
|
||||||
|
@patch.object(utils, 'is_leader')
|
||||||
@patch.object(utils, 'leader_set')
|
@patch.object(utils, 'leader_set')
|
||||||
def test_set_admin_password_config_username(self, leader_set):
|
def test_set_admin_password_config_username(self, leader_set, is_leader):
|
||||||
|
is_leader.return_value = True
|
||||||
self.test_config.set('admin-user', 'username')
|
self.test_config.set('admin-user', 'username')
|
||||||
utils.set_admin_passwd('secret')
|
utils.set_admin_passwd('secret')
|
||||||
leader_set.assert_called_once_with({'username_passwd': 'secret'})
|
leader_set.assert_called_once_with({'username_passwd': 'secret'})
|
||||||
|
|
||||||
|
@patch.object(utils, 'is_leader')
|
||||||
@patch.object(utils, 'leader_set')
|
@patch.object(utils, 'leader_set')
|
||||||
def test_set_admin_password_username(self, leader_set):
|
def test_set_admin_password_username(self, leader_set, is_leader):
|
||||||
|
is_leader.return_value = True
|
||||||
utils.set_admin_passwd('secret', user='username')
|
utils.set_admin_passwd('secret', user='username')
|
||||||
leader_set.assert_called_once_with({'username_passwd': 'secret'})
|
leader_set.assert_called_once_with({'username_passwd': 'secret'})
|
||||||
|
|
||||||
@ -1688,10 +1696,12 @@ class TestKeystoneUtils(CharmTestCase):
|
|||||||
utils.fernet_rotate()
|
utils.fernet_rotate()
|
||||||
self.subprocess.check_output.called_with(cmd)
|
self.subprocess.check_output.called_with(cmd)
|
||||||
|
|
||||||
|
@patch.object(utils, 'is_leader')
|
||||||
@patch.object(utils, 'leader_set')
|
@patch.object(utils, 'leader_set')
|
||||||
@patch('os.listdir')
|
@patch('os.listdir')
|
||||||
def test_key_leader_set(self, listdir, leader_set):
|
def test_key_leader_set(self, listdir, leader_set, is_leader):
|
||||||
listdir.return_value = ['0', '1']
|
listdir.return_value = ['0', '1']
|
||||||
|
is_leader.return_value = True
|
||||||
self.time.time.return_value = "the-time"
|
self.time.time.return_value = "the-time"
|
||||||
with patch.object(builtins, 'open', mock_open(
|
with patch.object(builtins, 'open', mock_open(
|
||||||
read_data="some_data")):
|
read_data="some_data")):
|
||||||
@ -1881,6 +1891,7 @@ class TestKeystoneUtils(CharmTestCase):
|
|||||||
mock_leader_get.assert_called_with('_charm-keystone-admin_passwd')
|
mock_leader_get.assert_called_with('_charm-keystone-admin_passwd')
|
||||||
|
|
||||||
@patch.object(utils, 'get_manager')
|
@patch.object(utils, 'get_manager')
|
||||||
|
@patch.object(utils, 'is_leader')
|
||||||
@patch.object(utils, 'leader_set')
|
@patch.object(utils, 'leader_set')
|
||||||
@patch.object(utils, 'resolve_address')
|
@patch.object(utils, 'resolve_address')
|
||||||
@patch.object(utils, 'endpoint_url')
|
@patch.object(utils, 'endpoint_url')
|
||||||
@ -1895,6 +1906,7 @@ class TestKeystoneUtils(CharmTestCase):
|
|||||||
mock_endpoint_url,
|
mock_endpoint_url,
|
||||||
mock_resolve_address,
|
mock_resolve_address,
|
||||||
mock_leader_set,
|
mock_leader_set,
|
||||||
|
mock_is_leader,
|
||||||
mock_get_manager):
|
mock_get_manager):
|
||||||
configs = MagicMock()
|
configs = MagicMock()
|
||||||
mock_get_api_suffix.return_value = 'suffix'
|
mock_get_api_suffix.return_value = 'suffix'
|
||||||
@ -1902,6 +1914,7 @@ class TestKeystoneUtils(CharmTestCase):
|
|||||||
mock_endpoint_url.side_effect = (
|
mock_endpoint_url.side_effect = (
|
||||||
lambda x, y, z: 'http://{}:{}/{}'.format(x, y, z))
|
lambda x, y, z: 'http://{}:{}/{}'.format(x, y, z))
|
||||||
mock_leader_get.return_value = 'fakepassword'
|
mock_leader_get.return_value = 'fakepassword'
|
||||||
|
mock_is_leader.return_value = True
|
||||||
mock_get_manager().resolve_user_id.return_value = 'fakeid'
|
mock_get_manager().resolve_user_id.return_value = 'fakeid'
|
||||||
self.os_release.return_value = 'queens'
|
self.os_release.return_value = 'queens'
|
||||||
utils.bootstrap_keystone(configs=configs)
|
utils.bootstrap_keystone(configs=configs)
|
||||||
|
Loading…
Reference in New Issue
Block a user