diff options
author | Matthew Thode <prometheanfire@gentoo.org> | 2016-02-02 10:01:35 -0600 |
---|---|---|
committer | Matthew Thode <prometheanfire@gentoo.org> | 2016-02-02 10:09:17 -0600 |
commit | ab7179f257e6dd899085f66fc81bb6a79418eb05 (patch) | |
tree | 1e61129012867afee90937bed75990aec7b0f06d /sys-auth | |
parent | app-benchmarks/filebench: Fix metadata.xml (diff) | |
download | gentoo-ab7179f257e6dd899085f66fc81bb6a79418eb05.tar.gz gentoo-ab7179f257e6dd899085f66fc81bb6a79418eb05.tar.bz2 gentoo-ab7179f257e6dd899085f66fc81bb6a79418eb05.zip |
sys-auth/keystone: fixing bug 573658 CVE-2015-7546
Package-Manager: portage-2.2.26
Diffstat (limited to 'sys-auth')
-rw-r--r-- | sys-auth/keystone/files/CVE-2015-7546_8.0.1.patch | 216 | ||||
-rw-r--r-- | sys-auth/keystone/keystone-8.0.1-r1.ebuild | 226 |
2 files changed, 442 insertions, 0 deletions
diff --git a/sys-auth/keystone/files/CVE-2015-7546_8.0.1.patch b/sys-auth/keystone/files/CVE-2015-7546_8.0.1.patch new file mode 100644 index 000000000000..82bff1edbe94 --- /dev/null +++ b/sys-auth/keystone/files/CVE-2015-7546_8.0.1.patch @@ -0,0 +1,216 @@ +From bff03b5726fe5cac93d44a66715eea49b89c8cb0 Mon Sep 17 00:00:00 2001 +From: Brant Knudson <bknudson@us.ibm.com> +Date: Tue, 1 Dec 2015 11:09:14 -0600 +Subject: [PATCH] Add audit IDs to revocation events + +The revoked tokens' audit ID is now included in the data returned in +the revocation list. + +Closes-Bug: 1490804 +Change-Id: Ifcf88f1158bebddc4f927121fbf4136fb53b659f +(cherry picked from commit d5378f173da14a34ca010271477337879002d6d0) +Conflicts: + keystone/tests/unit/test_backend.py +--- + keystone/tests/unit/test_backend.py | 39 ++++++++++++++-------- + keystone/tests/unit/test_backend_sql.py | 3 +- + keystone/token/persistence/backends/kvs.py | 9 +++++ + keystone/token/persistence/backends/sql.py | 12 ++++++- + .../notes/bug-1490804-de58a9606edb31eb.yaml | 13 ++++++++ + 5 files changed, 61 insertions(+), 15 deletions(-) + create mode 100644 releasenotes/notes/bug-1490804-de58a9606edb31eb.yaml + +diff --git a/keystone/tests/unit/test_backend.py b/keystone/tests/unit/test_backend.py +index 2340645..1273736 100644 +--- a/keystone/tests/unit/test_backend.py ++++ b/keystone/tests/unit/test_backend.py +@@ -4426,7 +4426,9 @@ class TokenTests(object): + token_id = self._create_token_id() + data = {'id': token_id, 'a': 'b', + 'trust_id': None, +- 'user': {'id': 'testuserid'}} ++ 'user': {'id': 'testuserid'}, ++ 'token_data': {'access': {'token': { ++ 'audit_ids': [uuid.uuid4().hex]}}}} + data_ref = self.token_provider_api._persistence.create_token(token_id, + data) + expires = data_ref.pop('expires') +@@ -4461,7 +4463,8 @@ class TokenTests(object): + # FIXME(morganfainberg): These tokens look nothing like "Real" tokens. + # This should be fixed when token issuance is cleaned up. + data = {'id': token_id, 'a': 'b', +- 'user': {'id': user_id}} ++ 'user': {'id': user_id}, ++ 'access': {'token': {'audit_ids': [uuid.uuid4().hex]}}} + if tenant_id is not None: + data['tenant'] = {'id': tenant_id, 'name': tenant_id} + if tenant_id is NULL_OBJECT: +@@ -4470,7 +4473,7 @@ class TokenTests(object): + data['expires'] = expires + if trust_id is not None: + data['trust_id'] = trust_id +- data.setdefault('access', {}).setdefault('trust', {}) ++ data['access'].setdefault('trust', {}) + # Testuserid2 is used here since a trustee will be different in + # the cases of impersonation and therefore should not match the + # token's user_id. +@@ -4633,17 +4636,21 @@ class TokenTests(object): + + self.assertEqual(data_ref, new_data_ref) + +- def check_list_revoked_tokens(self, token_ids): +- revoked_ids = [x['id'] +- for x in self.token_provider_api.list_revoked_tokens()] ++ def check_list_revoked_tokens(self, token_infos): ++ revocation_list = self.token_provider_api.list_revoked_tokens() ++ revoked_ids = [x['id'] for x in revocation_list] ++ revoked_audit_ids = [x['audit_id'] for x in revocation_list] + self._assert_revoked_token_list_matches_token_persistence(revoked_ids) +- for token_id in token_ids: ++ for token_id, audit_id in token_infos: + self.assertIn(token_id, revoked_ids) ++ self.assertIn(audit_id, revoked_audit_ids) + + def delete_token(self): + token_id = uuid.uuid4().hex ++ audit_id = uuid.uuid4().hex + data = {'id_hash': token_id, 'id': token_id, 'a': 'b', +- 'user': {'id': 'testuserid'}} ++ 'user': {'id': 'testuserid'}, ++ 'token_data': {'token': {'audit_ids': [audit_id]}}} + data_ref = self.token_provider_api._persistence.create_token(token_id, + data) + self.token_provider_api._persistence.delete_token(token_id) +@@ -4655,7 +4662,7 @@ class TokenTests(object): + exception.TokenNotFound, + self.token_provider_api._persistence.delete_token, + data_ref['id']) +- return token_id ++ return (token_id, audit_id) + + def test_list_revoked_tokens_returns_empty_list(self): + revoked_ids = [x['id'] +@@ -4706,12 +4713,16 @@ class TokenTests(object): + token_data = {'id_hash': token_id, 'id': token_id, 'a': 'b', + 'expires': expire_time, + 'trust_id': None, +- 'user': {'id': 'testuserid'}} ++ 'user': {'id': 'testuserid'}, ++ 'token_data': {'token': { ++ 'audit_ids': [uuid.uuid4().hex]}}} + token2_id = uuid.uuid4().hex + token2_data = {'id_hash': token2_id, 'id': token2_id, 'a': 'b', + 'expires': expire_time, + 'trust_id': None, +- 'user': {'id': 'testuserid'}} ++ 'user': {'id': 'testuserid'}, ++ 'token_data': {'token': { ++ 'audit_ids': [uuid.uuid4().hex]}}} + # Create 2 Tokens. + self.token_provider_api._persistence.create_token(token_id, + token_data) +@@ -4746,7 +4757,8 @@ class TokenTests(object): + def _test_predictable_revoked_pki_token_id(self, hash_fn): + token_id = self._create_token_id() + token_id_hash = hash_fn(token_id).hexdigest() +- token = {'user': {'id': uuid.uuid4().hex}} ++ token = {'user': {'id': uuid.uuid4().hex}, ++ 'token_data': {'token': {'audit_ids': [uuid.uuid4().hex]}}} + + self.token_provider_api._persistence.create_token(token_id, token) + self.token_provider_api._persistence.delete_token(token_id) +@@ -4768,7 +4780,8 @@ class TokenTests(object): + + def test_predictable_revoked_uuid_token_id(self): + token_id = uuid.uuid4().hex +- token = {'user': {'id': uuid.uuid4().hex}} ++ token = {'user': {'id': uuid.uuid4().hex}, ++ 'token_data': {'token': {'audit_ids': [uuid.uuid4().hex]}}} + + self.token_provider_api._persistence.create_token(token_id, token) + self.token_provider_api._persistence.delete_token(token_id) +diff --git a/keystone/tests/unit/test_backend_sql.py b/keystone/tests/unit/test_backend_sql.py +index 69fac63..51221a3 100644 +--- a/keystone/tests/unit/test_backend_sql.py ++++ b/keystone/tests/unit/test_backend_sql.py +@@ -492,7 +492,8 @@ class SqlToken(SqlTests, test_backend.TokenTests): + # necessary. + + expected_query_args = (token_sql.TokenModel.id, +- token_sql.TokenModel.expires) ++ token_sql.TokenModel.expires, ++ token_sql.TokenModel.extra,) + + with mock.patch.object(token_sql, 'sql') as mock_sql: + tok = token_sql.Token() +diff --git a/keystone/token/persistence/backends/kvs.py b/keystone/token/persistence/backends/kvs.py +index 5193158..60f7931 100644 +--- a/keystone/token/persistence/backends/kvs.py ++++ b/keystone/token/persistence/backends/kvs.py +@@ -210,6 +210,15 @@ class Token(token.persistence.TokenDriverV8): + subsecond=True) + revoked_token_data['id'] = data['id'] + ++ token_data = data['token_data'] ++ if 'access' in token_data: ++ # It's a v2 token. ++ audit_ids = token_data['access']['token']['audit_ids'] ++ else: ++ # It's a v3 token. ++ audit_ids = token_data['token']['audit_ids'] ++ revoked_token_data['audit_id'] = audit_ids[0] ++ + token_list = self._get_key_or_default(self.revocation_key, default=[]) + if not isinstance(token_list, list): + # NOTE(morganfainberg): In the case that the revocation list is not +diff --git a/keystone/token/persistence/backends/sql.py b/keystone/token/persistence/backends/sql.py +index 6fc1d22..d677620 100644 +--- a/keystone/token/persistence/backends/sql.py ++++ b/keystone/token/persistence/backends/sql.py +@@ -228,13 +228,23 @@ class Token(token.persistence.TokenDriverV8): + session = sql.get_session() + tokens = [] + now = timeutils.utcnow() +- query = session.query(TokenModel.id, TokenModel.expires) ++ query = session.query(TokenModel.id, TokenModel.expires, ++ TokenModel.extra) + query = query.filter(TokenModel.expires > now) + token_references = query.filter_by(valid=False) + for token_ref in token_references: ++ token_data = token_ref[2]['token_data'] ++ if 'access' in token_data: ++ # It's a v2 token. ++ audit_ids = token_data['access']['token']['audit_ids'] ++ else: ++ # It's a v3 token. ++ audit_ids = token_data['token']['audit_ids'] ++ + record = { + 'id': token_ref[0], + 'expires': token_ref[1], ++ 'audit_id': audit_ids[0], + } + tokens.append(record) + return tokens +diff --git a/releasenotes/notes/bug-1490804-de58a9606edb31eb.yaml b/releasenotes/notes/bug-1490804-de58a9606edb31eb.yaml +new file mode 100644 +index 0000000..0d5c203 +--- /dev/null ++++ b/releasenotes/notes/bug-1490804-de58a9606edb31eb.yaml +@@ -0,0 +1,13 @@ ++--- ++features: ++ - > ++ [`bug 1490804 <https://bugs.launchpad.net/keystone/+bug/1490804>`_] ++ Audit IDs are included in the token revocation list. ++security: ++ - > ++ [`bug 1490804 <https://bugs.launchpad.net/keystone/+bug/1490804>`_] ++ [`CVE-2015-7546 <http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-7546>`_] ++ A bug is fixed where an attacker could avoid token revocation when the PKI ++ or PKIZ token provider is used. The complete remediation for this ++ vulnerability requires the corresponding fix in the keystonemiddleware ++ project. +-- +1.9.1 + diff --git a/sys-auth/keystone/keystone-8.0.1-r1.ebuild b/sys-auth/keystone/keystone-8.0.1-r1.ebuild new file mode 100644 index 000000000000..24a477c3d14e --- /dev/null +++ b/sys-auth/keystone/keystone-8.0.1-r1.ebuild @@ -0,0 +1,226 @@ +# Copyright 1999-2015 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Id$ + +EAPI=5 + +PYTHON_COMPAT=( python2_7 ) + +inherit distutils-r1 user + +DESCRIPTION="The Openstack authentication, authorization, and service catalog" +HOMEPAGE="https://launchpad.net/keystone" +SRC_URI="https://tarballs.openstack.org/${PN}/${P}.tar.gz" + +LICENSE="Apache-2.0" +SLOT="0" +KEYWORDS="~amd64 ~x86" +IUSE="+sqlite memcached mongo mysql postgres ldap test" +REQUIRED_USE="|| ( mysql postgres sqlite )" + +CDEPEND=">=dev-python/pbr-1.6[${PYTHON_USEDEP}]" +DEPEND=" + dev-python/setuptools[${PYTHON_USEDEP}] + ${CDEPEND} + test? ( + ${RDEPEND} + >=dev-python/bashate-0.2[${PYTHON_USEDEP}] + <=dev-python/bashate-0.3.2[${PYTHON_USEDEP}] + memcached? ( + >=dev-python/python-memcached-1.48[${PYTHON_USEDEP}] + <=dev-python/python-memcached-1.57[${PYTHON_USEDEP}] + ) + mongo? ( + >=dev-python/pymongo-2.6.3[${PYTHON_USEDEP}] + <dev-python/pymongo-3.2[${PYTHON_USEDEP}] + ) + ldap? ( + >=dev-python/python-ldap-2.4[$(python_gen_usedep 'python2_7')] + <=dev-python/python-ldap-2.4.20[$(python_gen_usedep 'python2_7')] + ~dev-python/ldappool-1.0[$(python_gen_usedep 'python2_7')] + ) + >=dev-python/coverage-3.6[${PYTHON_USEDEP}] + <=dev-python/coverage-4.0.3[${PYTHON_USEDEP}] + >=dev-python/fixtures-1.3.1[${PYTHON_USEDEP}] + <=dev-python/fixtures-1.4.0-r9999[${PYTHON_USEDEP}] + >=dev-python/lxml-2.3[${PYTHON_USEDEP}] + <=dev-python/lxml-3.5.0-r9999[${PYTHON_USEDEP}] + >=dev-python/mock-1.2[${PYTHON_USEDEP}] + <=dev-python/mock-1.3.0[${PYTHON_USEDEP}] + >=dev-python/oslotest-1.10.0[${PYTHON_USEDEP}] + <=dev-python/oslotest-2.0.0[${PYTHON_USEDEP}] + >=dev-python/sphinx-1.1.2[${PYTHON_USEDEP}] + !~dev-python/sphinx-1.2.0[${PYTHON_USEDEP}] + <dev-python/sphinx-1.3[${PYTHON_USEDEP}] + >=dev-python/webtest-2.0[${PYTHON_USEDEP}] + <=dev-python/webtest-2.0.20[${PYTHON_USEDEP}] + >=dev-python/subunit-0.0.18[${PYTHON_USEDEP}] + <=dev-python/subunit-1.2.0[${PYTHON_USEDEP}] + >=dev-python/testrepository-0.0.18[${PYTHON_USEDEP}] + <=dev-python/testrepository-0.0.20[${PYTHON_USEDEP}] + >=dev-python/testtools-1.4.0[${PYTHON_USEDEP}] + <=dev-python/testtools-1.8.1[${PYTHON_USEDEP}] + >=dev-python/oslo-sphinx-2.5.0[${PYTHON_USEDEP}] + <=dev-python/oslo-sphinx-4.1.0[${PYTHON_USEDEP}] + >=dev-python/tempest-lib-0.8.0[${PYTHON_USEDEP}] + <=dev-python/tempest-lib-0.11.0[${PYTHON_USEDEP}] + >=dev-python/requests-2.5.2[${PYTHON_USEDEP}] + !~dev-python/requests-2.8.0[${PYTHON_USEDEP}] + <=dev-python/requests-2.8.1[${PYTHON_USEDEP}] + >=dev-python/reno-0.1.1[${PYTHON_USEDEP}] + )" +RDEPEND=" + ${CDEPEND} + >=dev-python/webob-1.2.3-r1[${PYTHON_USEDEP}] + <=dev-python/webob-1.5.1[${PYTHON_USEDEP}] + ~dev-python/eventlet-0.17.4[${PYTHON_USEDEP}] + >=dev-python/greenlet-0.3.2[${PYTHON_USEDEP}] + <=dev-python/greenlet-0.4.9[${PYTHON_USEDEP}] + >=dev-python/pastedeploy-1.5.0[${PYTHON_USEDEP}] + <=dev-python/pastedeploy-1.5.2[${PYTHON_USEDEP}] + <=dev-python/paste-2.0.2[${PYTHON_USEDEP}] + >=dev-python/routes-1.12.3[${PYTHON_USEDEP}] + !~dev-python/routes-2.0[${PYTHON_USEDEP}] + !~dev-python/routes-2.1[$(python_gen_usedep 'python2_7')] + <=dev-python/routes-2.2[${PYTHON_USEDEP}] + >=dev-python/cryptography-1.0[${PYTHON_USEDEP}] + <=dev-python/cryptography-1.1.2-r9999[${PYTHON_USEDEP}] + >=dev-python/six-1.9.0[${PYTHON_USEDEP}] + <=dev-python/six-1.10.0-r9999[${PYTHON_USEDEP}] + sqlite? ( + >=dev-python/sqlalchemy-0.9.9[sqlite,${PYTHON_USEDEP}] + <dev-python/sqlalchemy-1.0.10[sqlite,${PYTHON_USEDEP}] + ) + mysql? ( + dev-python/mysql-python + >=dev-python/sqlalchemy-0.9.9[${PYTHON_USEDEP}] + <dev-python/sqlalchemy-1.0.10[${PYTHON_USEDEP}] + ) + postgres? ( + dev-python/psycopg:2 + >=dev-python/sqlalchemy-0.9.9[${PYTHON_USEDEP}] + <dev-python/sqlalchemy-1.0.10[${PYTHON_USEDEP}] + ) + >=dev-python/sqlalchemy-migrate-0.9.6[${PYTHON_USEDEP}] + <=dev-python/sqlalchemy-migrate-0.10.0[${PYTHON_USEDEP}] + >=dev-python/stevedore-1.5.0[${PYTHON_USEDEP}] + <=dev-python/stevedore-1.10.0[${PYTHON_USEDEP}] + >=dev-python/passlib-1.6[${PYTHON_USEDEP}] + <=dev-python/passlib-1.6.5[${PYTHON_USEDEP}] + >=dev-python/python-keystoneclient-1.6.0[${PYTHON_USEDEP}] + !~dev-python/python-keystoneclient-1.8.0[${PYTHON_USEDEP}] + <=dev-python/python-keystoneclient-2.0.0-r9999[${PYTHON_USEDEP}] + >=dev-python/keystonemiddleware-2.0.0[${PYTHON_USEDEP}] + !~dev-python/keystonemiddleware-2.4.0[${PYTHON_USEDEP}] + <=dev-python/keystonemiddleware-4.0.0-r9999[${PYTHON_USEDEP}] + >=dev-python/oslo-concurrency-2.3.0[${PYTHON_USEDEP}] + <=dev-python/oslo-concurrency-3.1.0[${PYTHON_USEDEP}] + >=dev-python/oslo-config-2.3.0[${PYTHON_USEDEP}] + <=dev-python/oslo-config-3.1.0[${PYTHON_USEDEP}] + >=dev-python/oslo-context-0.2.0[${PYTHON_USEDEP}] + <=dev-python/oslo-context-1.0.0[${PYTHON_USEDEP}] + >=dev-python/oslo-messaging-1.16.0[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-1.17.0[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-1.17.1[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-2.6.0[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-2.6.1[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-2.7.0[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-2.8.0[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-2.8.1[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-2.9.0[${PYTHON_USEDEP}] + !~dev-python/oslo-messaging-3.1.0[${PYTHON_USEDEP}] + <=dev-python/oslo-messaging-3.0.0[${PYTHON_USEDEP}] + >=dev-python/oslo-db-2.4.1[${PYTHON_USEDEP}] + <=dev-python/oslo-db-4.1.0[${PYTHON_USEDEP}] + >=dev-python/oslo-i18n-1.5.0[${PYTHON_USEDEP}] + <=dev-python/oslo-i18n-3.1.0[${PYTHON_USEDEP}] + >=dev-python/oslo-log-1.8.0[${PYTHON_USEDEP}] + <=dev-python/oslo-log-2.1.0[${PYTHON_USEDEP}] + >=dev-python/oslo-middleware-2.8.0[${PYTHON_USEDEP}] + <=dev-python/oslo-middleware-3.3.0[${PYTHON_USEDEP}] + >=dev-python/oslo-policy-0.5.0[${PYTHON_USEDEP}] + <=dev-python/oslo-policy-1.1.0[${PYTHON_USEDEP}] + >=dev-python/oslo-serialization-1.4.0[${PYTHON_USEDEP}] + <=dev-python/oslo-serialization-2.1.0[${PYTHON_USEDEP}] + >=dev-python/oslo-service-0.7.0[${PYTHON_USEDEP}] + <=dev-python/oslo-service-1.1.0[${PYTHON_USEDEP}] + >=dev-python/oslo-utils-2.0.0[${PYTHON_USEDEP}] + !~dev-python/oslo-utils-2.6.0[${PYTHON_USEDEP}] + <=dev-python/oslo-utils-3.2.0[${PYTHON_USEDEP}] + >=dev-python/oauthlib-0.6.0[${PYTHON_USEDEP}] + <=dev-python/oauthlib-1.0.3[${PYTHON_USEDEP}] + >=dev-python/pysaml2-2.4.0[${PYTHON_USEDEP}] + <=dev-python/pysaml2-4.0.0[${PYTHON_USEDEP}] + >=dev-python/dogpile-cache-0.5.4[${PYTHON_USEDEP}] + <=dev-python/dogpile-cache-0.5.7[${PYTHON_USEDEP}] + >=dev-python/jsonschema-2.0.0[${PYTHON_USEDEP}] + !~dev-python/jsonschema-2.5.0[${PYTHON_USEDEP}] + <dev-python/jsonschema-3.0.0[${PYTHON_USEDEP}] + ~dev-python/pycadf-1.1.0[${PYTHON_USEDEP}] + <=dev-python/pycadf-2.0.1[${PYTHON_USEDEP}] + ~dev-python/msgpack-0.4.6[${PYTHON_USEDEP}]" + +PATCHES=( + "${FILESDIR}/CVE-2015-7546_8.0.1.patch" +) + +pkg_setup() { + enewgroup keystone + enewuser keystone -1 -1 /var/lib/keystone keystone +} + +python_prepare_all() { + # it's in git, but not in the tarball..... + sed -i '/^hacking/d' test-requirements.txt || die + mkdir -p ${PN}/tests/tmp/ || die + cp etc/keystone-paste.ini ${PN}/tests/tmp/ || die + distutils-r1_python_prepare_all +} + +# Ignore (naughty) test_.py files & 1 test that connect to the network +#-I 'test_keystoneclient*' \ +python_test() { + nosetests -I 'test_keystoneclient*' \ + -e test_static_translated_string_is_Message \ + -e test_get_token_id_error_handling \ + -e test_provider_token_expiration_validation \ + -e test_import --process-restartworker --process-timeout=60 || die "testsuite failed under python2.7" +} + +python_install() { + distutils-r1_python_install + newconfd "${FILESDIR}/keystone.confd" keystone + newinitd "${FILESDIR}/keystone.initd" keystone + + diropts -m 0750 + keepdir /etc/keystone /var/log/keystone + insinto /etc/keystone + insopts -m0640 -okeystone -gkeystone + doins etc/keystone.conf.sample etc/logging.conf.sample + doins etc/default_catalog.templates etc/policy.json + doins etc/policy.v3cloudsample.json etc/keystone-paste.ini + insinto /etc/keystone/httpd + doins httpd/keystone.py httpd/wsgi-keystone.conf + + fowners keystone:keystone /etc/keystone /var/log/keystone +} + +pkg_postinst() { + elog "You might want to run:" + elog "emerge --config =${CATEGORY}/${PF}" + elog "if this is a new install." + elog "If you have not already configured your openssl installation" + elog "please do it by modifying /etc/ssl/openssl.cnf" + elog "BEFORE issuing the configuration command." + elog "Otherwise default values will be used." +} + +pkg_config() { + if [ ! -d "${ROOT}"/etc/keystone/ssl ] ; then + einfo "Press ENTER to configure the keystone PKI, or Control-C to abort now..." + read + "${ROOT}"/usr/bin/keystone-manage pki_setup --keystone-user keystone --keystone-group keystone + else + einfo "keystone PKI certificates directory already present, skipping configuration" + fi +} |